diff options
Diffstat (limited to 'cmd2/utils.py')
-rw-r--r-- | cmd2/utils.py | 112 |
1 files changed, 70 insertions, 42 deletions
diff --git a/cmd2/utils.py b/cmd2/utils.py index c88df0ec..7e5f5af5 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -102,6 +102,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 @@ -117,13 +118,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 @@ -172,8 +180,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 @@ -460,8 +467,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. @@ -544,6 +551,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'] @@ -574,8 +582,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 @@ -586,11 +594,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: @@ -601,6 +607,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. @@ -678,6 +685,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. @@ -697,8 +705,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 @@ -736,13 +750,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 @@ -766,7 +788,9 @@ def align_text(text: str, alignment: TextAlignment, *, fill_char: str = ' ', import io import shutil - from . import ansi + from . import ( + ansi, + ) if width is None: width = shutil.get_terminal_size().columns @@ -823,7 +847,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) @@ -874,8 +898,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 @@ -893,12 +918,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 @@ -916,12 +941,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 @@ -939,8 +964,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: @@ -965,7 +989,10 @@ 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 + + from . import ( + ansi, + ) # Handle tabs line = line.replace('\t', ' ' * tab_width) @@ -1028,7 +1055,9 @@ def get_styles_in_text(text: str) -> Dict[int, str]: :param text: text to search for style sequences """ - from . import ansi + from . import ( + ansi, + ) start = 0 styles = collections.OrderedDict() @@ -1091,16 +1120,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 |