Add even more type hints to function arguments
This commit is contained in:
parent
1bfdb14be4
commit
c009ab2d41
4 changed files with 145 additions and 80 deletions
|
|
@ -212,7 +212,7 @@ def popen_program(
|
||||||
minimized: bool = False,
|
minimized: bool = False,
|
||||||
pipe: bool = False,
|
pipe: bool = False,
|
||||||
shell: bool = False,
|
shell: bool = False,
|
||||||
**kwargs: dict[Any, Any],
|
**kwargs,
|
||||||
) -> subprocess.Popen:
|
) -> subprocess.Popen:
|
||||||
"""Run program and return a subprocess.Popen object."""
|
"""Run program and return a subprocess.Popen object."""
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
|
|
@ -242,7 +242,7 @@ def run_program(
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
pipe: bool = True,
|
pipe: bool = True,
|
||||||
shell: bool = False,
|
shell: bool = False,
|
||||||
**kwargs: dict[Any, Any],
|
**kwargs,
|
||||||
) -> subprocess.CompletedProcess:
|
) -> subprocess.CompletedProcess:
|
||||||
"""Run program and return a subprocess.CompletedProcess object."""
|
"""Run program and return a subprocess.CompletedProcess object."""
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Any
|
from typing import Any, Callable, Iterable
|
||||||
|
|
||||||
from prompt_toolkit import prompt
|
from prompt_toolkit import prompt
|
||||||
|
from prompt_toolkit.document import Document
|
||||||
from prompt_toolkit.validation import Validator, ValidationError
|
from prompt_toolkit.validation import Validator, ValidationError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -38,12 +39,12 @@ PLATFORM = platform.system()
|
||||||
# Classes
|
# Classes
|
||||||
class InputChoiceValidator(Validator):
|
class InputChoiceValidator(Validator):
|
||||||
"""Validate that input is one of the provided choices."""
|
"""Validate that input is one of the provided choices."""
|
||||||
def __init__(self, choices, allow_empty=False):
|
def __init__(self, choices: Iterable[str], allow_empty: bool = False):
|
||||||
self.allow_empty = allow_empty
|
self.allow_empty = allow_empty
|
||||||
self.choices = [str(c).upper() for c in choices]
|
self.choices = [str(c).upper() for c in choices]
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def validate(self, document) -> None:
|
def validate(self, document: Document) -> None:
|
||||||
text = document.text
|
text = document.text
|
||||||
if not (text or self.allow_empty):
|
if not (text or self.allow_empty):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
|
|
@ -58,7 +59,7 @@ class InputChoiceValidator(Validator):
|
||||||
|
|
||||||
class InputNotEmptyValidator(Validator):
|
class InputNotEmptyValidator(Validator):
|
||||||
"""Validate that input is not empty."""
|
"""Validate that input is not empty."""
|
||||||
def validate(self, document) -> None:
|
def validate(self, document: Document) -> None:
|
||||||
text = document.text
|
text = document.text
|
||||||
if not text:
|
if not text:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
|
|
@ -68,11 +69,11 @@ class InputNotEmptyValidator(Validator):
|
||||||
|
|
||||||
class InputTicketIDValidator(Validator):
|
class InputTicketIDValidator(Validator):
|
||||||
"""Validate that input resembles a ticket ID."""
|
"""Validate that input resembles a ticket ID."""
|
||||||
def __init__(self, allow_empty=False):
|
def __init__(self, allow_empty: bool = False):
|
||||||
self.allow_empty = allow_empty
|
self.allow_empty = allow_empty
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def validate(self, document) -> None:
|
def validate(self, document: Document) -> None:
|
||||||
text = document.text
|
text = document.text
|
||||||
if not (text or self.allow_empty):
|
if not (text or self.allow_empty):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
|
|
@ -87,11 +88,11 @@ class InputTicketIDValidator(Validator):
|
||||||
|
|
||||||
class InputYesNoValidator(Validator):
|
class InputYesNoValidator(Validator):
|
||||||
"""Validate that input is a yes or no."""
|
"""Validate that input is a yes or no."""
|
||||||
def __init__(self, allow_empty=False):
|
def __init__(self, allow_empty: bool = False):
|
||||||
self.allow_empty = allow_empty
|
self.allow_empty = allow_empty
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def validate(self, document) -> None:
|
def validate(self, document: Document) -> None:
|
||||||
text = document.text
|
text = document.text
|
||||||
if not (text or self.allow_empty):
|
if not (text or self.allow_empty):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
|
|
@ -113,7 +114,7 @@ class Menu():
|
||||||
1. All entry names are unique.
|
1. All entry names are unique.
|
||||||
2. All action entry names start with different letters.
|
2. All action entry names start with different letters.
|
||||||
"""
|
"""
|
||||||
def __init__(self, title='[Untitled Menu]'):
|
def __init__(self, title: str = '[Untitled Menu]'):
|
||||||
self.actions = OrderedDict()
|
self.actions = OrderedDict()
|
||||||
self.options = OrderedDict()
|
self.options = OrderedDict()
|
||||||
self.sets = OrderedDict()
|
self.sets = OrderedDict()
|
||||||
|
|
@ -236,7 +237,7 @@ class Menu():
|
||||||
# Done
|
# Done
|
||||||
return valid_answers
|
return valid_answers
|
||||||
|
|
||||||
def _resolve_selection(self, selection) -> tuple[str, dict[Any, Any]]:
|
def _resolve_selection(self, selection: str) -> tuple[str, dict[Any, Any]]:
|
||||||
"""Get menu item based on user selection, returns tuple."""
|
"""Get menu item based on user selection, returns tuple."""
|
||||||
offset = 1
|
offset = 1
|
||||||
resolved_selection = tuple()
|
resolved_selection = tuple()
|
||||||
|
|
@ -267,7 +268,7 @@ class Menu():
|
||||||
# Done
|
# Done
|
||||||
return resolved_selection
|
return resolved_selection
|
||||||
|
|
||||||
def _update(self, single_selection=True, settings_mode=False) -> None:
|
def _update(self, single_selection: bool = True, settings_mode: bool = False) -> None:
|
||||||
"""Update menu items in preparation for printing to screen."""
|
"""Update menu items in preparation for printing to screen."""
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
|
|
@ -305,7 +306,8 @@ class Menu():
|
||||||
no_checkboxes=True,
|
no_checkboxes=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _update_entry_selection_status(self, entry, toggle=True, status=None) -> None:
|
def _update_entry_selection_status(
|
||||||
|
self, entry: str, toggle: bool = True, status: bool = False) -> None:
|
||||||
"""Update entry selection status either directly or by toggling."""
|
"""Update entry selection status either directly or by toggling."""
|
||||||
if entry in self.sets:
|
if entry in self.sets:
|
||||||
# Update targets not the set itself
|
# Update targets not the set itself
|
||||||
|
|
@ -319,14 +321,14 @@ class Menu():
|
||||||
else:
|
else:
|
||||||
section[entry]['Selected'] = status
|
section[entry]['Selected'] = status
|
||||||
|
|
||||||
def _update_set_selection_status(self, targets, status) -> None:
|
def _update_set_selection_status(self, targets: Iterable[str], status: bool) -> None:
|
||||||
"""Select or deselect options based on targets and status."""
|
"""Select or deselect options based on targets and status."""
|
||||||
for option, details in self.options.items():
|
for option, details in self.options.items():
|
||||||
# If (new) status is True and this option is a target then select
|
# If (new) status is True and this option is a target then select
|
||||||
# Otherwise deselect
|
# Otherwise deselect
|
||||||
details['Selected'] = status and option in targets
|
details['Selected'] = status and option in targets
|
||||||
|
|
||||||
def _user_select(self, prompt_msg) -> str:
|
def _user_select(self, prompt_msg: str) -> str:
|
||||||
"""Show menu and select an entry, returns str."""
|
"""Show menu and select an entry, returns str."""
|
||||||
menu_text = self._generate_menu_text()
|
menu_text = self._generate_menu_text()
|
||||||
valid_answers = self._get_valid_answers()
|
valid_answers = self._get_valid_answers()
|
||||||
|
|
@ -343,19 +345,19 @@ class Menu():
|
||||||
# Done
|
# Done
|
||||||
return answer
|
return answer
|
||||||
|
|
||||||
def add_action(self, name, details=None) -> None:
|
def add_action(self, name: str, details: dict[Any, Any] | None = None) -> None:
|
||||||
"""Add action to menu."""
|
"""Add action to menu."""
|
||||||
details = details if details else {}
|
details = details if details else {}
|
||||||
details['Selected'] = details.get('Selected', False)
|
details['Selected'] = details.get('Selected', False)
|
||||||
self.actions[name] = details
|
self.actions[name] = details
|
||||||
|
|
||||||
def add_option(self, name, details=None) -> None:
|
def add_option(self, name: str, details: dict[Any, Any] | None = None) -> None:
|
||||||
"""Add option to menu."""
|
"""Add option to menu."""
|
||||||
details = details if details else {}
|
details = details if details else {}
|
||||||
details['Selected'] = details.get('Selected', False)
|
details['Selected'] = details.get('Selected', False)
|
||||||
self.options[name] = details
|
self.options[name] = details
|
||||||
|
|
||||||
def add_set(self, name, details=None) -> None:
|
def add_set(self, name: str, details: dict[Any, Any] | None = None) -> None:
|
||||||
"""Add set to menu."""
|
"""Add set to menu."""
|
||||||
details = details if details else {}
|
details = details if details else {}
|
||||||
details['Selected'] = details.get('Selected', False)
|
details['Selected'] = details.get('Selected', False)
|
||||||
|
|
@ -367,14 +369,16 @@ class Menu():
|
||||||
# Add set
|
# Add set
|
||||||
self.sets[name] = details
|
self.sets[name] = details
|
||||||
|
|
||||||
def add_toggle(self, name, details=None) -> None:
|
def add_toggle(self, name: str, details: dict[Any, Any] | None = None) -> None:
|
||||||
"""Add toggle to menu."""
|
"""Add toggle to menu."""
|
||||||
details = details if details else {}
|
details = details if details else {}
|
||||||
details['Selected'] = details.get('Selected', False)
|
details['Selected'] = details.get('Selected', False)
|
||||||
self.toggles[name] = details
|
self.toggles[name] = details
|
||||||
|
|
||||||
def advanced_select(
|
def advanced_select(
|
||||||
self, prompt_msg='Please make a selection: ') -> tuple[str, dict[Any, Any]]:
|
self,
|
||||||
|
prompt_msg: str = 'Please make a selection: ',
|
||||||
|
) -> tuple[str, dict[Any, Any]]:
|
||||||
"""Display menu and make multiple selections, returns tuple.
|
"""Display menu and make multiple selections, returns tuple.
|
||||||
|
|
||||||
NOTE: Menu is displayed until an action entry is selected.
|
NOTE: Menu is displayed until an action entry is selected.
|
||||||
|
|
@ -394,7 +398,9 @@ class Menu():
|
||||||
return selected_entry
|
return selected_entry
|
||||||
|
|
||||||
def settings_select(
|
def settings_select(
|
||||||
self, prompt_msg='Please make a selection: ') -> tuple[str, dict[Any, Any]]:
|
self,
|
||||||
|
prompt_msg: str = 'Please make a selection: ',
|
||||||
|
) -> tuple[str, dict[Any, Any]]:
|
||||||
"""Display menu and make multiple selections, returns tuple.
|
"""Display menu and make multiple selections, returns tuple.
|
||||||
|
|
||||||
NOTE: Menu is displayed until an action entry is selected.
|
NOTE: Menu is displayed until an action entry is selected.
|
||||||
|
|
@ -423,8 +429,10 @@ class Menu():
|
||||||
return selected_entry
|
return selected_entry
|
||||||
|
|
||||||
def simple_select(
|
def simple_select(
|
||||||
self, prompt_msg='Please make a selection: ',
|
self,
|
||||||
update=True) -> tuple[str, dict[Any, Any]]:
|
prompt_msg: str = 'Please make a selection: ',
|
||||||
|
update: bool = True,
|
||||||
|
) -> tuple[str, dict[Any, Any]]:
|
||||||
"""Display menu and make a single selection, returns tuple."""
|
"""Display menu and make a single selection, returns tuple."""
|
||||||
if update:
|
if update:
|
||||||
self._update()
|
self._update()
|
||||||
|
|
@ -441,7 +449,7 @@ class TryAndPrint():
|
||||||
The errors and warning attributes are used to allow fine-tuned results
|
The errors and warning attributes are used to allow fine-tuned results
|
||||||
based on exception names.
|
based on exception names.
|
||||||
"""
|
"""
|
||||||
def __init__(self, msg_bad='FAILED', msg_good='SUCCESS'):
|
def __init__(self, msg_bad: str = 'FAILED', msg_good: str = 'SUCCESS'):
|
||||||
self.catch_all = True
|
self.catch_all = True
|
||||||
self.indent = INDENT
|
self.indent = INDENT
|
||||||
self.list_errors = ['GenericError']
|
self.list_errors = ['GenericError']
|
||||||
|
|
@ -451,7 +459,7 @@ class TryAndPrint():
|
||||||
self.verbose = False
|
self.verbose = False
|
||||||
self.width = WIDTH
|
self.width = WIDTH
|
||||||
|
|
||||||
def _format_exception_message(self, _exception) -> str:
|
def _format_exception_message(self, _exception: Exception) -> str:
|
||||||
"""Format using the exception's args or name, returns str."""
|
"""Format using the exception's args or name, returns str."""
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
'Formatting exception: %s, %s',
|
'Formatting exception: %s, %s',
|
||||||
|
|
@ -498,7 +506,11 @@ class TryAndPrint():
|
||||||
# Done
|
# Done
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def _format_function_output(self, output, msg_good) -> str:
|
def _format_function_output(
|
||||||
|
self,
|
||||||
|
output: list | subprocess.CompletedProcess,
|
||||||
|
msg_good: str,
|
||||||
|
) -> str:
|
||||||
"""Format function output for use in try_and_print(), returns str."""
|
"""Format function output for use in try_and_print(), returns str."""
|
||||||
LOG.debug('Formatting output: %s', output)
|
LOG.debug('Formatting output: %s', output)
|
||||||
|
|
||||||
|
|
@ -536,27 +548,33 @@ class TryAndPrint():
|
||||||
# Done
|
# Done
|
||||||
return result_msg
|
return result_msg
|
||||||
|
|
||||||
def _log_result(self, message, result_msg) -> None:
|
def _log_result(self, message: str, result_msg: str) -> None:
|
||||||
"""Log result text without color formatting."""
|
"""Log result text without color formatting."""
|
||||||
log_text = f'{" "*self.indent}{message:<{self.width}}{result_msg}'
|
log_text = f'{" "*self.indent}{message:<{self.width}}{result_msg}'
|
||||||
for line in log_text.splitlines():
|
for line in log_text.splitlines():
|
||||||
line = strip_colors(line)
|
line = strip_colors(line)
|
||||||
LOG.info(line)
|
LOG.info(line)
|
||||||
|
|
||||||
def add_error(self, exception_name) -> None:
|
def add_error(self, exception_name: str) -> None:
|
||||||
"""Add exception name to error list."""
|
"""Add exception name to error list."""
|
||||||
if exception_name not in self.list_errors:
|
if exception_name not in self.list_errors:
|
||||||
self.list_errors.append(exception_name)
|
self.list_errors.append(exception_name)
|
||||||
|
|
||||||
def add_warning(self, exception_name) -> None:
|
def add_warning(self, exception_name: str) -> None:
|
||||||
"""Add exception name to warning list."""
|
"""Add exception name to warning list."""
|
||||||
if exception_name not in self.list_warnings:
|
if exception_name not in self.list_warnings:
|
||||||
self.list_warnings.append(exception_name)
|
self.list_warnings.append(exception_name)
|
||||||
|
|
||||||
def run(
|
def run(
|
||||||
self, message, function, *args,
|
self,
|
||||||
catch_all=None, msg_good=None, verbose=None,
|
message: str,
|
||||||
**kwargs) -> dict[str, Any]:
|
function: Callable,
|
||||||
|
*args: Iterable[Any],
|
||||||
|
catch_all: bool | None = None,
|
||||||
|
msg_good: str | None = None,
|
||||||
|
verbose: bool | None = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""Run a function and print the results, returns results as dict.
|
"""Run a function and print the results, returns results as dict.
|
||||||
|
|
||||||
If catch_all is True then (nearly) all exceptions will be caught.
|
If catch_all is True then (nearly) all exceptions will be caught.
|
||||||
|
|
@ -594,8 +612,8 @@ class TryAndPrint():
|
||||||
verbose = verbose if verbose is not None else self.verbose
|
verbose = verbose if verbose is not None else self.verbose
|
||||||
|
|
||||||
# Build exception tuples
|
# Build exception tuples
|
||||||
e_exceptions = tuple(get_exception(e) for e in self.list_errors)
|
e_exceptions: tuple = tuple(get_exception(e) for e in self.list_errors)
|
||||||
w_exceptions = tuple(get_exception(e) for e in self.list_warnings)
|
w_exceptions: tuple = tuple(get_exception(e) for e in self.list_warnings)
|
||||||
|
|
||||||
# Run function and catch exceptions
|
# Run function and catch exceptions
|
||||||
print(f'{" "*self.indent}{message:<{self.width}}', end='', flush=True)
|
print(f'{" "*self.indent}{message:<{self.width}}', end='', flush=True)
|
||||||
|
|
@ -644,7 +662,10 @@ class TryAndPrint():
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def abort(
|
def abort(
|
||||||
prompt_msg='Aborted.', show_prompt_msg=True, return_code=1) -> None:
|
prompt_msg: str = 'Aborted.',
|
||||||
|
show_prompt_msg: bool = True,
|
||||||
|
return_code: int = 1,
|
||||||
|
) -> None:
|
||||||
"""Abort script."""
|
"""Abort script."""
|
||||||
print_warning(prompt_msg)
|
print_warning(prompt_msg)
|
||||||
if show_prompt_msg:
|
if show_prompt_msg:
|
||||||
|
|
@ -653,7 +674,7 @@ def abort(
|
||||||
sys.exit(return_code)
|
sys.exit(return_code)
|
||||||
|
|
||||||
|
|
||||||
def ask(prompt_msg) -> bool:
|
def ask(prompt_msg: str) -> bool:
|
||||||
"""Prompt the user with a Y/N question, returns bool."""
|
"""Prompt the user with a Y/N question, returns bool."""
|
||||||
validator = InputYesNoValidator()
|
validator = InputYesNoValidator()
|
||||||
|
|
||||||
|
|
@ -670,7 +691,7 @@ def ask(prompt_msg) -> bool:
|
||||||
raise ValueError(f'Invalid answer given: {response}')
|
raise ValueError(f'Invalid answer given: {response}')
|
||||||
|
|
||||||
|
|
||||||
def beep(repeat=1) -> None:
|
def beep(repeat: int = 1) -> None:
|
||||||
"""Play system bell with optional repeat."""
|
"""Play system bell with optional repeat."""
|
||||||
while repeat >= 1:
|
while repeat >= 1:
|
||||||
# Print bell char without a newline
|
# Print bell char without a newline
|
||||||
|
|
@ -679,7 +700,7 @@ def beep(repeat=1) -> None:
|
||||||
repeat -= 1
|
repeat -= 1
|
||||||
|
|
||||||
|
|
||||||
def choice(prompt_msg, choices) -> str:
|
def choice(prompt_msg: str, choices: Iterable[str]) -> str:
|
||||||
"""Choose an option from a provided list, returns str.
|
"""Choose an option from a provided list, returns str.
|
||||||
|
|
||||||
Choices provided will be converted to uppercase and returned as such.
|
Choices provided will be converted to uppercase and returned as such.
|
||||||
|
|
@ -697,7 +718,7 @@ def choice(prompt_msg, choices) -> str:
|
||||||
return response.upper()
|
return response.upper()
|
||||||
|
|
||||||
|
|
||||||
def fix_prompt(message) -> str:
|
def fix_prompt(message: str) -> str:
|
||||||
"""Fix prompt, returns str."""
|
"""Fix prompt, returns str."""
|
||||||
if not message:
|
if not message:
|
||||||
message = 'Input text: '
|
message = 'Input text: '
|
||||||
|
|
@ -708,7 +729,7 @@ def fix_prompt(message) -> str:
|
||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
def get_exception(name) -> Exception:
|
def get_exception(name: str) -> Exception:
|
||||||
"""Get exception by name, returns exception object.
|
"""Get exception by name, returns exception object.
|
||||||
|
|
||||||
[Doctest]
|
[Doctest]
|
||||||
|
|
@ -757,7 +778,9 @@ def get_ticket_id() -> str:
|
||||||
|
|
||||||
|
|
||||||
def input_text(
|
def input_text(
|
||||||
prompt_msg='Enter text: ', allow_empty=False, validator=None,
|
prompt_msg: str = 'Enter text: ',
|
||||||
|
allow_empty: bool = False,
|
||||||
|
validator: Validator | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Get input from user, returns str."""
|
"""Get input from user, returns str."""
|
||||||
prompt_msg = fix_prompt(prompt_msg)
|
prompt_msg = fix_prompt(prompt_msg)
|
||||||
|
|
@ -793,12 +816,18 @@ def major_exception() -> None:
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
def pause(prompt_msg='Press Enter to continue... ') -> None:
|
def pause(prompt_msg: str = 'Press Enter to continue... ') -> None:
|
||||||
"""Simple pause implementation."""
|
"""Simple pause implementation."""
|
||||||
input_text(prompt_msg, allow_empty=True)
|
input_text(prompt_msg, allow_empty=True)
|
||||||
|
|
||||||
|
|
||||||
def print_colored(strings, colors, log=False, sep=' ', **kwargs) -> None:
|
def print_colored(
|
||||||
|
strings: Iterable[str] | str,
|
||||||
|
colors: Iterable[str | None] | str,
|
||||||
|
log: bool = False,
|
||||||
|
sep: str = ' ',
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
"""Prints strings in the colors specified."""
|
"""Prints strings in the colors specified."""
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
'strings: %s, colors: %s, sep: %s, kwargs: %s',
|
'strings: %s, colors: %s, sep: %s, kwargs: %s',
|
||||||
|
|
@ -816,7 +845,7 @@ def print_colored(strings, colors, log=False, sep=' ', **kwargs) -> None:
|
||||||
LOG.info(strip_colors(msg))
|
LOG.info(strip_colors(msg))
|
||||||
|
|
||||||
|
|
||||||
def print_error(msg, log=True, **kwargs) -> None:
|
def print_error(msg: str, log: bool = True, **kwargs) -> None:
|
||||||
"""Prints message in RED and log as ERROR."""
|
"""Prints message in RED and log as ERROR."""
|
||||||
if 'file' not in kwargs:
|
if 'file' not in kwargs:
|
||||||
# Only set if not specified
|
# Only set if not specified
|
||||||
|
|
@ -826,14 +855,14 @@ def print_error(msg, log=True, **kwargs) -> None:
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
|
|
||||||
|
|
||||||
def print_info(msg, log=True, **kwargs) -> None:
|
def print_info(msg: str, log: bool = True, **kwargs) -> None:
|
||||||
"""Prints message in BLUE and log as INFO."""
|
"""Prints message in BLUE and log as INFO."""
|
||||||
print_colored(msg, 'BLUE', **kwargs)
|
print_colored(msg, 'BLUE', **kwargs)
|
||||||
if log:
|
if log:
|
||||||
LOG.info(msg)
|
LOG.info(msg)
|
||||||
|
|
||||||
|
|
||||||
def print_report(report, indent=None, log=True) -> None:
|
def print_report(report: list[str], indent=None, log: bool = True) -> None:
|
||||||
"""Print report to screen and optionally to log."""
|
"""Print report to screen and optionally to log."""
|
||||||
for line in report:
|
for line in report:
|
||||||
if indent:
|
if indent:
|
||||||
|
|
@ -843,21 +872,21 @@ def print_report(report, indent=None, log=True) -> None:
|
||||||
LOG.info(strip_colors(line))
|
LOG.info(strip_colors(line))
|
||||||
|
|
||||||
|
|
||||||
def print_standard(msg, log=True, **kwargs) -> None:
|
def print_standard(msg: str, log: bool = True, **kwargs) -> None:
|
||||||
"""Prints message and log as INFO."""
|
"""Prints message and log as INFO."""
|
||||||
print(msg, **kwargs)
|
print(msg, **kwargs)
|
||||||
if log:
|
if log:
|
||||||
LOG.info(msg)
|
LOG.info(msg)
|
||||||
|
|
||||||
|
|
||||||
def print_success(msg, log=True, **kwargs) -> None:
|
def print_success(msg: str, log: bool = True, **kwargs) -> None:
|
||||||
"""Prints message in GREEN and log as INFO."""
|
"""Prints message in GREEN and log as INFO."""
|
||||||
print_colored(msg, 'GREEN', **kwargs)
|
print_colored(msg, 'GREEN', **kwargs)
|
||||||
if log:
|
if log:
|
||||||
LOG.info(msg)
|
LOG.info(msg)
|
||||||
|
|
||||||
|
|
||||||
def print_warning(msg, log=True, **kwargs) -> None:
|
def print_warning(msg: str, log: bool = True, **kwargs) -> None:
|
||||||
"""Prints message in YELLOW and log as WARNING."""
|
"""Prints message in YELLOW and log as WARNING."""
|
||||||
if 'file' not in kwargs:
|
if 'file' not in kwargs:
|
||||||
# Only set if not specified
|
# Only set if not specified
|
||||||
|
|
@ -867,7 +896,7 @@ def print_warning(msg, log=True, **kwargs) -> None:
|
||||||
LOG.warning(msg)
|
LOG.warning(msg)
|
||||||
|
|
||||||
|
|
||||||
def set_title(title) -> None:
|
def set_title(title: str) -> None:
|
||||||
"""Set window title."""
|
"""Set window title."""
|
||||||
LOG.debug('title: %s', title)
|
LOG.debug('title: %s', title)
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
|
|
@ -876,14 +905,19 @@ def set_title(title) -> None:
|
||||||
print_error('Setting the title is only supported under Windows.')
|
print_error('Setting the title is only supported under Windows.')
|
||||||
|
|
||||||
|
|
||||||
def show_data(message, data, color=None, indent=None, width=None) -> None:
|
def show_data(
|
||||||
|
message: str,
|
||||||
|
data: Any,
|
||||||
|
color: str | None = None,
|
||||||
|
indent: int | None = None,
|
||||||
|
width: int | None = None,
|
||||||
|
) -> None:
|
||||||
"""Display info using default or provided indent and width."""
|
"""Display info using default or provided indent and width."""
|
||||||
colors = (None, color if color else None)
|
|
||||||
indent = INDENT if indent is None else indent
|
indent = INDENT if indent is None else indent
|
||||||
width = WIDTH if width is None else width
|
width = WIDTH if width is None else width
|
||||||
print_colored(
|
print_colored(
|
||||||
(f'{" "*indent}{message:<{width}}', data),
|
(f'{" "*indent}{message:<{width}}', data),
|
||||||
colors,
|
(None, color if color else None),
|
||||||
log=True,
|
log=True,
|
||||||
sep='',
|
sep='',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from wk.exe import run_program
|
from wk.exe import run_program
|
||||||
from wk.std import PLATFORM
|
from wk.std import PLATFORM
|
||||||
|
|
||||||
|
|
@ -13,7 +15,7 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def capture_pane(pane_id=None) -> str:
|
def capture_pane(pane_id: str | None = None) -> str:
|
||||||
"""Capture text from current or target pane, returns str."""
|
"""Capture text from current or target pane, returns str."""
|
||||||
cmd = ['tmux', 'capture-pane', '-p']
|
cmd = ['tmux', 'capture-pane', '-p']
|
||||||
if pane_id:
|
if pane_id:
|
||||||
|
|
@ -24,7 +26,7 @@ def capture_pane(pane_id=None) -> str:
|
||||||
return proc.stdout.strip()
|
return proc.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
def clear_pane(pane_id=None) -> None:
|
def clear_pane(pane_id: str | None = None) -> None:
|
||||||
"""Clear pane buffer for current or target pane."""
|
"""Clear pane buffer for current or target pane."""
|
||||||
commands = [
|
commands = [
|
||||||
['tmux', 'send-keys', '-R'],
|
['tmux', 'send-keys', '-R'],
|
||||||
|
|
@ -38,7 +40,7 @@ def clear_pane(pane_id=None) -> None:
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
def fix_layout(layout, forced=False) -> None:
|
def fix_layout(layout: dict[str, dict[str, Any]], forced: bool = False) -> None:
|
||||||
"""Fix pane sizes based on layout."""
|
"""Fix pane sizes based on layout."""
|
||||||
resize_kwargs = []
|
resize_kwargs = []
|
||||||
|
|
||||||
|
|
@ -110,7 +112,7 @@ def fix_layout(layout, forced=False) -> None:
|
||||||
resize_pane(worker, height=layout['Workers']['height'])
|
resize_pane(worker, height=layout['Workers']['height'])
|
||||||
|
|
||||||
|
|
||||||
def get_pane_size(pane_id=None) -> tuple[int, int]:
|
def get_pane_size(pane_id: str | None = None) -> tuple[int, int]:
|
||||||
"""Get current or target pane size, returns tuple."""
|
"""Get current or target pane size, returns tuple."""
|
||||||
cmd = ['tmux', 'display', '-p']
|
cmd = ['tmux', 'display', '-p']
|
||||||
if pane_id:
|
if pane_id:
|
||||||
|
|
@ -141,7 +143,7 @@ def get_window_size() -> tuple[int, int]:
|
||||||
return (width, height)
|
return (width, height)
|
||||||
|
|
||||||
|
|
||||||
def kill_all_panes(pane_id=None) -> None:
|
def kill_all_panes(pane_id: str | None = None) -> None:
|
||||||
"""Kill all panes except for the current or target pane."""
|
"""Kill all panes except for the current or target pane."""
|
||||||
cmd = ['tmux', 'kill-pane', '-a']
|
cmd = ['tmux', 'kill-pane', '-a']
|
||||||
if pane_id:
|
if pane_id:
|
||||||
|
|
@ -151,7 +153,7 @@ def kill_all_panes(pane_id=None) -> None:
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
def kill_pane(*pane_ids) -> None:
|
def kill_pane(*pane_ids: str) -> None:
|
||||||
"""Kill pane(s) by id."""
|
"""Kill pane(s) by id."""
|
||||||
cmd = ['tmux', 'kill-pane', '-t']
|
cmd = ['tmux', 'kill-pane', '-t']
|
||||||
|
|
||||||
|
|
@ -160,7 +162,7 @@ def kill_pane(*pane_ids) -> None:
|
||||||
run_program(cmd+[pane_id], check=False)
|
run_program(cmd+[pane_id], check=False)
|
||||||
|
|
||||||
|
|
||||||
def layout_needs_fixed(layout) -> bool:
|
def layout_needs_fixed(layout: dict[str, dict[str, Any]]) -> bool:
|
||||||
"""Check if layout needs fixed, returns bool."""
|
"""Check if layout needs fixed, returns bool."""
|
||||||
needs_fixed = False
|
needs_fixed = False
|
||||||
|
|
||||||
|
|
@ -189,7 +191,7 @@ def layout_needs_fixed(layout) -> bool:
|
||||||
return needs_fixed
|
return needs_fixed
|
||||||
|
|
||||||
|
|
||||||
def poll_pane(pane_id) -> bool:
|
def poll_pane(pane_id: str) -> bool:
|
||||||
"""Check if pane exists, returns bool."""
|
"""Check if pane exists, returns bool."""
|
||||||
cmd = ['tmux', 'list-panes', '-F', '#D']
|
cmd = ['tmux', 'list-panes', '-F', '#D']
|
||||||
|
|
||||||
|
|
@ -202,8 +204,12 @@ def poll_pane(pane_id) -> bool:
|
||||||
|
|
||||||
|
|
||||||
def prep_action(
|
def prep_action(
|
||||||
cmd=None, working_dir=None, text=None,
|
cmd: str | None = None,
|
||||||
watch_file=None, watch_cmd='cat') -> list[str]:
|
working_dir: pathlib.Path | str | None = None,
|
||||||
|
text: str | None = None,
|
||||||
|
watch_file: pathlib.Path | str | None = None,
|
||||||
|
watch_cmd: str = 'cat',
|
||||||
|
) -> list[str]:
|
||||||
"""Prep action to perform during a tmux call, returns list.
|
"""Prep action to perform during a tmux call, returns list.
|
||||||
|
|
||||||
This will prep for running a basic command, displaying text on screen,
|
This will prep for running a basic command, displaying text on screen,
|
||||||
|
|
@ -253,7 +259,7 @@ def prep_action(
|
||||||
return action_cmd
|
return action_cmd
|
||||||
|
|
||||||
|
|
||||||
def prep_file(path) -> None:
|
def prep_file(path: pathlib.Path | str) -> None:
|
||||||
"""Check if file exists and create empty file if not."""
|
"""Check if file exists and create empty file if not."""
|
||||||
path = pathlib.Path(path).resolve()
|
path = pathlib.Path(path).resolve()
|
||||||
try:
|
try:
|
||||||
|
|
@ -263,7 +269,11 @@ def prep_file(path) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def resize_pane(pane_id=None, width=None, height=None) -> None:
|
def resize_pane(
|
||||||
|
pane_id: str | None = None,
|
||||||
|
width: int | None = None,
|
||||||
|
height: int | None = None,
|
||||||
|
) -> None:
|
||||||
"""Resize current or target pane.
|
"""Resize current or target pane.
|
||||||
|
|
||||||
NOTE: kwargs is only here to make calling this function easier
|
NOTE: kwargs is only here to make calling this function easier
|
||||||
|
|
@ -288,7 +298,7 @@ def resize_pane(pane_id=None, width=None, height=None) -> None:
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
def respawn_pane(pane_id, **action) -> None:
|
def respawn_pane(pane_id: str, **action) -> None:
|
||||||
"""Respawn pane with action."""
|
"""Respawn pane with action."""
|
||||||
cmd = ['tmux', 'respawn-pane', '-k', '-t', pane_id]
|
cmd = ['tmux', 'respawn-pane', '-k', '-t', pane_id]
|
||||||
cmd.extend(prep_action(**action))
|
cmd.extend(prep_action(**action))
|
||||||
|
|
@ -298,9 +308,12 @@ def respawn_pane(pane_id, **action) -> None:
|
||||||
|
|
||||||
|
|
||||||
def split_window(
|
def split_window(
|
||||||
lines=None, percent=None,
|
lines: int | None = None,
|
||||||
behind=False, vertical=False,
|
percent: int | None = None,
|
||||||
target_id=None, **action) -> str:
|
behind: bool = False,
|
||||||
|
vertical: bool = False,
|
||||||
|
target_id: str | None = None,
|
||||||
|
**action) -> str:
|
||||||
"""Split tmux window, run action, and return pane_id as str."""
|
"""Split tmux window, run action, and return pane_id as str."""
|
||||||
cmd = ['tmux', 'split-window', '-d', '-PF', '#D']
|
cmd = ['tmux', 'split-window', '-d', '-PF', '#D']
|
||||||
|
|
||||||
|
|
@ -333,7 +346,7 @@ def split_window(
|
||||||
return proc.stdout.strip()
|
return proc.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
def zoom_pane(pane_id=None) -> None:
|
def zoom_pane(pane_id: str | None = None) -> None:
|
||||||
"""Toggle zoom status for current or target pane."""
|
"""Toggle zoom status for current or target pane."""
|
||||||
cmd = ['tmux', 'resize-pane', '-Z']
|
cmd = ['tmux', 'resize-pane', '-Z']
|
||||||
if pane_id:
|
if pane_id:
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ TMUX_LAYOUT = { # NOTE: This needs to be in order from top to bottom
|
||||||
# Classes
|
# Classes
|
||||||
class TUI():
|
class TUI():
|
||||||
"""Object for tracking TUI elements."""
|
"""Object for tracking TUI elements."""
|
||||||
def __init__(self, title_text=None):
|
def __init__(self, title_text: str | None = None):
|
||||||
self.layout = deepcopy(TMUX_LAYOUT)
|
self.layout = deepcopy(TMUX_LAYOUT)
|
||||||
self.side_width = TMUX_SIDE_WIDTH
|
self.side_width = TMUX_SIDE_WIDTH
|
||||||
self.title_text = title_text if title_text else 'Title Text'
|
self.title_text = title_text if title_text else 'Title Text'
|
||||||
|
|
@ -44,7 +44,11 @@ class TUI():
|
||||||
atexit.register(tmux.kill_all_panes)
|
atexit.register(tmux.kill_all_panes)
|
||||||
|
|
||||||
def add_info_pane(
|
def add_info_pane(
|
||||||
self, lines=None, percent=None, update_layout=True, **tmux_args,
|
self,
|
||||||
|
lines: int | None = None,
|
||||||
|
percent: int | None = None,
|
||||||
|
update_layout: bool = True,
|
||||||
|
**tmux_args,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add info pane."""
|
"""Add info pane."""
|
||||||
if not (lines or percent):
|
if not (lines or percent):
|
||||||
|
|
@ -78,7 +82,12 @@ class TUI():
|
||||||
# Add pane
|
# Add pane
|
||||||
self.layout['Info']['Panes'].append(tmux.split_window(**tmux_args))
|
self.layout['Info']['Panes'].append(tmux.split_window(**tmux_args))
|
||||||
|
|
||||||
def add_title_pane(self, line1, line2=None, colors=None) -> None:
|
def add_title_pane(
|
||||||
|
self,
|
||||||
|
line1: str,
|
||||||
|
line2: str | None = None,
|
||||||
|
colors: list[str] | None = None,
|
||||||
|
) -> None:
|
||||||
"""Add pane to title row."""
|
"""Add pane to title row."""
|
||||||
lines = [line1, line2]
|
lines = [line1, line2]
|
||||||
colors = colors if colors else self.title_colors.copy()
|
colors = colors if colors else self.title_colors.copy()
|
||||||
|
|
@ -105,7 +114,11 @@ class TUI():
|
||||||
self.layout['Title']['Panes'].append(tmux.split_window(**tmux_args))
|
self.layout['Title']['Panes'].append(tmux.split_window(**tmux_args))
|
||||||
|
|
||||||
def add_worker_pane(
|
def add_worker_pane(
|
||||||
self, lines=None, percent=None, update_layout=True, **tmux_args,
|
self,
|
||||||
|
lines: int | None = None,
|
||||||
|
percent: int | None = None,
|
||||||
|
update_layout: bool = True,
|
||||||
|
**tmux_args,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add worker pane."""
|
"""Add worker pane."""
|
||||||
height = lines
|
height = lines
|
||||||
|
|
@ -142,7 +155,7 @@ class TUI():
|
||||||
"""Clear current pane height and update layout."""
|
"""Clear current pane height and update layout."""
|
||||||
self.layout['Current'].pop('height', None)
|
self.layout['Current'].pop('height', None)
|
||||||
|
|
||||||
def fix_layout(self, forced=True) -> None:
|
def fix_layout(self, forced: bool = True) -> None:
|
||||||
"""Fix tmux layout based on self.layout."""
|
"""Fix tmux layout based on self.layout."""
|
||||||
try:
|
try:
|
||||||
tmux.fix_layout(self.layout, forced=forced)
|
tmux.fix_layout(self.layout, forced=forced)
|
||||||
|
|
@ -208,19 +221,24 @@ class TUI():
|
||||||
self.layout['Workers']['Panes'].clear()
|
self.layout['Workers']['Panes'].clear()
|
||||||
tmux.kill_pane(*panes)
|
tmux.kill_pane(*panes)
|
||||||
|
|
||||||
def set_current_pane_height(self, height) -> None:
|
def set_current_pane_height(self, height: int) -> None:
|
||||||
"""Set current pane height and update layout."""
|
"""Set current pane height and update layout."""
|
||||||
self.layout['Current']['height'] = height
|
self.layout['Current']['height'] = height
|
||||||
tmux.resize_pane(height=height)
|
tmux.resize_pane(height=height)
|
||||||
|
|
||||||
def set_progress_file(self, progress_file) -> None:
|
def set_progress_file(self, progress_file: str) -> None:
|
||||||
"""Set the file to use for the progresse pane."""
|
"""Set the file to use for the progresse pane."""
|
||||||
tmux.respawn_pane(
|
tmux.respawn_pane(
|
||||||
pane_id=self.layout['Progress']['Panes'][0],
|
pane_id=self.layout['Progress']['Panes'][0],
|
||||||
watch_file=progress_file,
|
watch_file=progress_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_title(self, line1, line2=None, colors=None) -> None:
|
def set_title(
|
||||||
|
self,
|
||||||
|
line1: str,
|
||||||
|
line2: str | None = None,
|
||||||
|
colors: list[str] | None = None,
|
||||||
|
) -> None:
|
||||||
"""Set title text."""
|
"""Set title text."""
|
||||||
self.title_text = line1
|
self.title_text = line1
|
||||||
self.title_text_line2 = line2 if line2 else ''
|
self.title_text_line2 = line2 if line2 else ''
|
||||||
|
|
@ -251,7 +269,7 @@ class TUI():
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def fix_layout(layout, forced=False) -> None:
|
def fix_layout(layout, forced: bool = False) -> None:
|
||||||
"""Fix pane sizes based on layout."""
|
"""Fix pane sizes based on layout."""
|
||||||
resize_kwargs = []
|
resize_kwargs = []
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue