diff options
Diffstat (limited to 'cmd2/utils.py')
-rw-r--r-- | cmd2/utils.py | 103 |
1 files changed, 62 insertions, 41 deletions
diff --git a/cmd2/utils.py b/cmd2/utils.py index b58cdb96..d2dd5b18 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -12,10 +12,9 @@ import re import subprocess import sys import threading - import unicodedata from enum import Enum -from typing import Any, Callable, Dict, IO, Iterable, List, Optional, TextIO, Type, Union +from typing import IO, Any, Callable, Dict, Iterable, List, Optional, TextIO, Type, Union from . import constants @@ -88,6 +87,7 @@ class CompletionError(Exception): - A previous command line argument that determines the data set being completed is invalid - Tab completion hints """ + def __init__(self, *args, apply_style: bool = True, **kwargs): """ Initializer for CompletionError @@ -103,13 +103,20 @@ class CompletionError(Exception): class Settable: """Used to configure a cmd2 instance member to be settable via the set command in the CLI""" - def __init__(self, name: str, val_type: Callable, description: str, *, - onchange_cb: Callable[[str, Any, Any], Any] = None, - choices: Iterable = None, - choices_function: Optional[Callable] = None, - choices_method: Optional[Callable] = None, - completer_function: Optional[Callable] = None, - completer_method: Optional[Callable] = None): + + def __init__( + self, + name: str, + val_type: Callable, + description: str, + *, + onchange_cb: Callable[[str, Any, Any], Any] = None, + choices: Iterable = None, + choices_function: Optional[Callable] = None, + choices_method: Optional[Callable] = None, + completer_function: Optional[Callable] = None, + completer_method: Optional[Callable] = None + ): """ Settable Initializer @@ -158,8 +165,7 @@ class Settable: self.completer_method = completer_method -def namedtuple_with_defaults(typename: str, field_names: Union[str, List[str]], - default_values: collections_abc.Iterable = ()): +def namedtuple_with_defaults(typename: str, field_names: Union[str, List[str]], default_values: collections_abc.Iterable = ()): """ Convenience function for defining a namedtuple with default values @@ -446,8 +452,8 @@ class StdSim: Class to simulate behavior of sys.stdout or sys.stderr. Stores contents in internal buffer and optionally echos to the inner stream it is simulating. """ - def __init__(self, inner_stream, *, echo: bool = False, - encoding: str = 'utf-8', errors: str = 'replace') -> None: + + def __init__(self, inner_stream, *, echo: bool = False, encoding: str = 'utf-8', errors: str = 'replace') -> None: """ StdSim Initializer :param inner_stream: the wrapped stream. Should be a TextIO or StdSim instance. @@ -530,6 +536,7 @@ class ByteBuf: """ Used by StdSim to write binary data and stores the actual bytes written """ + # Used to know when to flush the StdSim NEWLINES = [b'\n', b'\r'] @@ -560,8 +567,8 @@ class ProcReader: Used to capture stdout and stderr from a Popen process if any of those were set to subprocess.PIPE. If neither are pipes, then the process will run normally and no output will be captured. """ - def __init__(self, proc: subprocess.Popen, stdout: Union[StdSim, TextIO], - stderr: Union[StdSim, TextIO]) -> None: + + def __init__(self, proc: subprocess.Popen, stdout: Union[StdSim, TextIO], stderr: Union[StdSim, TextIO]) -> None: """ ProcReader initializer :param proc: the Popen process being read from @@ -572,11 +579,9 @@ class ProcReader: self._stdout = stdout self._stderr = stderr - self._out_thread = threading.Thread(name='out_thread', target=self._reader_thread_func, - kwargs={'read_stdout': True}) + self._out_thread = threading.Thread(name='out_thread', target=self._reader_thread_func, kwargs={'read_stdout': True}) - self._err_thread = threading.Thread(name='out_thread', target=self._reader_thread_func, - kwargs={'read_stdout': False}) + self._err_thread = threading.Thread(name='out_thread', target=self._reader_thread_func, kwargs={'read_stdout': False}) # Start the reader threads for pipes only if self._proc.stdout is not None: @@ -587,6 +592,7 @@ class ProcReader: def send_sigint(self) -> None: """Send a SIGINT to the process similar to if <Ctrl>+C were pressed""" import signal + if sys.platform.startswith('win'): # cmd2 started the Windows process in a new process group. Therefore # a CTRL_C_EVENT can't be sent to it. Send a CTRL_BREAK_EVENT instead. @@ -664,6 +670,7 @@ class ContextFlag: while a critical code section has set the flag to True. Because signal handling is always done on the main thread, this class is not thread-safe since there is no need. """ + def __init__(self) -> None: # When this flag has a positive value, it is considered set. # When it is 0, it is not set. It should never go below 0. @@ -683,8 +690,14 @@ class ContextFlag: class RedirectionSavedState: """Created by each command to store information required to restore state after redirection""" - def __init__(self, self_stdout: Union[StdSim, IO[str]], sys_stdout: Union[StdSim, IO[str]], - pipe_proc_reader: Optional[ProcReader], saved_redirecting: bool) -> None: + + def __init__( + self, + self_stdout: Union[StdSim, IO[str]], + sys_stdout: Union[StdSim, IO[str]], + pipe_proc_reader: Optional[ProcReader], + saved_redirecting: bool, + ) -> None: """ RedirectionSavedState initializer :param self_stdout: saved value of Cmd.stdout @@ -722,13 +735,21 @@ def basic_complete(text: str, line: str, begidx: int, endidx: int, match_against class TextAlignment(Enum): """Horizontal text alignment""" + LEFT = 1 CENTER = 2 RIGHT = 3 -def align_text(text: str, alignment: TextAlignment, *, fill_char: str = ' ', - width: Optional[int] = None, tab_width: int = 4, truncate: bool = False) -> str: +def align_text( + text: str, + alignment: TextAlignment, + *, + fill_char: str = ' ', + width: Optional[int] = None, + tab_width: int = 4, + truncate: bool = False +) -> str: """ Align text for display within a given width. Supports characters with display widths greater than 1. ANSI style sequences do not count toward the display width. If text has line breaks, then each line is aligned @@ -809,7 +830,7 @@ def align_text(text: str, alignment: TextAlignment, *, fill_char: str = ' ', line_width = ansi.style_aware_wcswidth(line) if line_width == -1: - raise(ValueError("Text to align contains an unprintable character")) + raise (ValueError("Text to align contains an unprintable character")) # Get the styles in this line line_styles = get_styles_in_text(line) @@ -860,8 +881,9 @@ def align_text(text: str, alignment: TextAlignment, *, fill_char: str = ' ', return text_buf.getvalue() -def align_left(text: str, *, fill_char: str = ' ', width: Optional[int] = None, - tab_width: int = 4, truncate: bool = False) -> str: +def align_left( + text: str, *, fill_char: str = ' ', width: Optional[int] = None, tab_width: int = 4, truncate: bool = False +) -> str: """ Left align text for display within a given width. Supports characters with display widths greater than 1. ANSI style sequences do not count toward the display width. If text has line breaks, then each line is aligned @@ -879,12 +901,12 @@ def align_left(text: str, *, fill_char: str = ' ', width: Optional[int] = None, :raises: ValueError if text or fill_char contains an unprintable character :raises: ValueError if width is less than 1 """ - return align_text(text, TextAlignment.LEFT, fill_char=fill_char, width=width, - tab_width=tab_width, truncate=truncate) + return align_text(text, TextAlignment.LEFT, fill_char=fill_char, width=width, tab_width=tab_width, truncate=truncate) -def align_center(text: str, *, fill_char: str = ' ', width: Optional[int] = None, - tab_width: int = 4, truncate: bool = False) -> str: +def align_center( + text: str, *, fill_char: str = ' ', width: Optional[int] = None, tab_width: int = 4, truncate: bool = False +) -> str: """ Center text for display within a given width. Supports characters with display widths greater than 1. ANSI style sequences do not count toward the display width. If text has line breaks, then each line is aligned @@ -902,12 +924,12 @@ def align_center(text: str, *, fill_char: str = ' ', width: Optional[int] = None :raises: ValueError if text or fill_char contains an unprintable character :raises: ValueError if width is less than 1 """ - return align_text(text, TextAlignment.CENTER, fill_char=fill_char, width=width, - tab_width=tab_width, truncate=truncate) + return align_text(text, TextAlignment.CENTER, fill_char=fill_char, width=width, tab_width=tab_width, truncate=truncate) -def align_right(text: str, *, fill_char: str = ' ', width: Optional[int] = None, - tab_width: int = 4, truncate: bool = False) -> str: +def align_right( + text: str, *, fill_char: str = ' ', width: Optional[int] = None, tab_width: int = 4, truncate: bool = False +) -> str: """ Right align text for display within a given width. Supports characters with display widths greater than 1. ANSI style sequences do not count toward the display width. If text has line breaks, then each line is aligned @@ -925,8 +947,7 @@ def align_right(text: str, *, fill_char: str = ' ', width: Optional[int] = None, :raises: ValueError if text or fill_char contains an unprintable character :raises: ValueError if width is less than 1 """ - return align_text(text, TextAlignment.RIGHT, fill_char=fill_char, width=width, - tab_width=tab_width, truncate=truncate) + return align_text(text, TextAlignment.RIGHT, fill_char=fill_char, width=width, tab_width=tab_width, truncate=truncate) def truncate_line(line: str, max_width: int, *, tab_width: int = 4) -> str: @@ -951,6 +972,7 @@ def truncate_line(line: str, max_width: int, *, tab_width: int = 4) -> str: :raises: ValueError if max_width is less than 1 """ import io + from . import ansi # Handle tabs @@ -1077,16 +1099,15 @@ def get_defining_class(meth) -> Type: """ if isinstance(meth, functools.partial): return get_defining_class(meth.func) - if inspect.ismethod(meth) or (inspect.isbuiltin(meth) - and getattr(meth, '__self__') is not None - and getattr(meth.__self__, '__class__')): + if inspect.ismethod(meth) or ( + inspect.isbuiltin(meth) and getattr(meth, '__self__') is not None and getattr(meth.__self__, '__class__') + ): for cls in inspect.getmro(meth.__self__.__class__): if meth.__name__ in cls.__dict__: return cls meth = getattr(meth, '__func__', meth) # fallback to __qualname__ parsing if inspect.isfunction(meth): - cls = getattr(inspect.getmodule(meth), - meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0]) + cls = getattr(inspect.getmodule(meth), meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0]) if isinstance(cls, type): return cls return getattr(meth, '__objclass__', None) # handle special descriptor objects |