From 86bcf647deae2d045db06f6a5f4b5da054bb4054 Mon Sep 17 00:00:00 2001 From: Avram Lubkin Date: Thu, 23 Jan 2020 17:08:37 -0500 Subject: [PATCH] Fixes for linting --- .pylintrc | 5 +- bin/colorchart.py | 4 +- bin/progress_bar.py | 8 +-- blessed/color.py | 18 ++--- blessed/colorspace.py | 4 ++ blessed/formatters.py | 90 +++++++++++++---------- blessed/keyboard.py | 79 ++++++++++---------- blessed/sequences.py | 33 ++++++--- blessed/terminal.py | 155 +++++++++++++++++++++++++--------------- blessed/win_terminal.py | 3 +- tox.ini | 13 +++- 11 files changed, 253 insertions(+), 159 deletions(-) diff --git a/.pylintrc b/.pylintrc index 9714cf85..a086abc7 100644 --- a/.pylintrc +++ b/.pylintrc @@ -15,11 +15,12 @@ disable= I, fixme, c-extension-no-member, - ungrouped-imports + ungrouped-imports, + useless-object-inheritance # Python 2 [FORMAT] max-line-length: 100 -good-names=ks,fd,_ +good-names=ks,fd,_,x,y [PARAMETER_DOCUMENTATION] default-docstring-type=sphinx diff --git a/bin/colorchart.py b/bin/colorchart.py index 57d247b6..1f54c02a 100644 --- a/bin/colorchart.py +++ b/bin/colorchart.py @@ -1,5 +1,5 @@ # encoding: utf-8 -"""Utility to show X11 colors in 24-bit and down-converted to 256, 16, and 8 color The time to +"""Utility to show X11 colors in 24-bit and downconverted to 256, 16, and 8 color The time to generate the table is displayed to give an indication of how long each algorithm takes compared to the others.""" # std imports @@ -30,7 +30,7 @@ def sort_colors(): def draw_chart(term): - """Draw a chart of each X11 color represented as in 24-bit and as down-converted to 256, 16, and + """Draw a chart of each X11 color represented as in 24-bit and as downconverted to 256, 16, and 8 color with the currently configured algorithm.""" sys.stdout.write(term.home) width = term.width diff --git a/bin/progress_bar.py b/bin/progress_bar.py index e8b0a8d8..ebea4426 100755 --- a/bin/progress_bar.py +++ b/bin/progress_bar.py @@ -2,10 +2,10 @@ """ Example application for the 'blessed' Terminal library for python. -This isn't a real progress bar, just a sample "animated prompt" of sorts that -demonstrates the separate move_x() and move_yx() capabilities, made mainly to -test the `hpa' compatibility for 'screen' terminal type which fails to provide -one, but blessed recognizes that it actually does, and provides a proxy. +This isn't a real progress bar, just a sample "animated prompt" of sorts that demonstrates the +separate move_x() and move_yx() capabilities, made mainly to test the `hpa' compatibility for +'screen' terminal type which fails to provide one, but blessed recognizes that it actually does, and +provides a proxy. """ from __future__ import print_function diff --git a/blessed/color.py b/blessed/color.py index 9a42be4f..242de8ea 100644 --- a/blessed/color.py +++ b/blessed/color.py @@ -21,9 +21,9 @@ def rgb_to_xyz(red, green, blue): """ Convert standard RGB color to XYZ color. - :arg red: RGB value of Red. - :arg green: RGB value of Green. - :arg blue: RGB value of Blue. + :arg int red: RGB value of Red. + :arg int green: RGB value of Green. + :arg int blue: RGB value of Blue. :returns: Tuple (X, Y, Z) representing XYZ color :rtype: tuple @@ -51,9 +51,9 @@ def xyz_to_lab(x_val, y_val, z_val): """ Convert XYZ color to CIE-Lab color. - :arg x_val: XYZ value of X. - :arg y_val: XYZ value of Y. - :arg z_val: XYZ value of Z. + :arg float x_val: XYZ value of X. + :arg float y_val: XYZ value of Y. + :arg float z_val: XYZ value of Z. :returns: Tuple (L, a, b) representing CIE-Lab color :rtype: tuple @@ -81,9 +81,9 @@ def rgb_to_lab(red, green, blue): """ Convert RGB color to CIE-Lab color. - :arg red: RGB value of Red. - :arg green: RGB value of Green. - :arg blue: RGB value of Blue. + :arg int red: RGB value of Red. + :arg int green: RGB value of Green. + :arg int blue: RGB value of Blue. :returns: Tuple (L, a, b) representing CIE-Lab color :rtype: tuple diff --git a/blessed/colorspace.py b/blessed/colorspace.py index 0b0e972f..2e15915c 100644 --- a/blessed/colorspace.py +++ b/blessed/colorspace.py @@ -1,4 +1,6 @@ """ +Color reference data. + References, - https://github.com/freedesktop/xorg-rgb/blob/master/rgb.txt @@ -24,6 +26,8 @@ class RGBColor(collections.namedtuple("RGBColor", ["red", "green", "blue"])): + """Named tuple for an RGB color definition.""" + def __str__(self): return '#{0:02x}{1:02x}{2:02x}'.format(*self) diff --git a/blessed/formatters.py b/blessed/formatters.py index 7beaf8e0..44735a7a 100644 --- a/blessed/formatters.py +++ b/blessed/formatters.py @@ -20,6 +20,7 @@ def _make_colors(): Return set of valid colors and their derivatives. :rtype: set + :returns: Color names with prefixes """ colors = set() # basic CGA foreground color, background, high intensity, and bold @@ -58,18 +59,18 @@ class ParameterizingString(six.text_type): u'\x1b[91mcolor #9\x1b(B\x1b[m' """ - def __new__(cls, *args): + def __new__(cls, cap, normal=u'', name=u''): + # pylint: disable = missing-return-doc, missing-return-type-doc """ Class constructor accepting 3 positional arguments. - :arg cap: parameterized string suitable for curses.tparm() - :arg normal: terminating sequence for this capability (optional). - :arg name: name of this terminal capability (optional). + :arg str cap: parameterized string suitable for curses.tparm() + :arg str normal: terminating sequence for this capability (optional). + :arg str name: name of this terminal capability (optional). """ - assert args and len(args) < 4, args - new = six.text_type.__new__(cls, args[0]) - new._normal = args[1] if len(args) > 1 else u'' - new._name = args[2] if len(args) > 2 else u'' + new = six.text_type.__new__(cls, cap) + new._normal = normal + new._name = name return new def __call__(self, *args): @@ -80,7 +81,10 @@ def __call__(self, *args): ``*args``, followed by the terminating sequence (self.normal) into a :class:`FormattingString` capable of being called. + :raises TypeError: Mismatch between capability and arguments + :raises curses.error: :func:`curses.tparm` raised an exception :rtype: :class:`FormattingString` or :class:`NullCallableString` + :returns: Callable string for given parameters """ try: # Re-encode the cap, because tparm() takes a bytestring in Python @@ -133,22 +137,23 @@ class ParameterizingProxyString(six.text_type): u'\x1b[10G' """ - def __new__(cls, *args): + def __new__(cls, fmt_pair, normal=u'', name=u''): + # pylint: disable = missing-return-doc, missing-return-type-doc """ Class constructor accepting 4 positional arguments. - :arg fmt: format string suitable for displaying terminal sequences. - :arg callable: receives __call__ arguments for formatting fmt. - :arg normal: terminating sequence for this capability (optional). - :arg name: name of this terminal capability (optional). + :arg tuple fmt_pair: Two element tuple containing: + - format string suitable for displaying terminal sequences + - callable suitable for receiving __call__ arguments for formatting string + :arg str normal: terminating sequence for this capability (optional). + :arg str name: name of this terminal capability (optional). """ - assert args and len(args) < 4, args - assert isinstance(args[0], tuple), args[0] - assert callable(args[0][1]), args[0][1] - new = six.text_type.__new__(cls, args[0][0]) - new._fmt_args = args[0][1] - new._normal = args[1] if len(args) > 1 else u'' - new._name = args[2] if len(args) > 2 else u'' + assert isinstance(fmt_pair, tuple), fmt_pair + assert callable(fmt_pair[1]), fmt_pair[1] + new = six.text_type.__new__(cls, fmt_pair[0]) + new._fmt_args = fmt_pair[1] + new._normal = normal + new._name = name return new def __call__(self, *args): @@ -161,10 +166,12 @@ def __call__(self, *args): given capability. :rtype: FormattingString + :returns: Callable string for given parameters """ return FormattingString(self.format(*self._fmt_args(*args)), self._normal) + class FormattingString(six.text_type): r""" A Unicode string which doubles as a callable. @@ -182,20 +189,26 @@ class FormattingString(six.text_type): u'\x1b[94mBig Blue\x1b(B\x1b[m' """ - def __new__(cls, *args): + def __new__(cls, sequence, normal=u''): + # pylint: disable = missing-return-doc, missing-return-type-doc """ Class constructor accepting 2 positional arguments. - :arg sequence: terminal attribute sequence. - :arg normal: terminating sequence for this attribute (optional). + :arg str sequence: terminal attribute sequence. + :arg str normal: terminating sequence for this attribute (optional). """ - assert 1 <= len(args) <= 2, args - new = six.text_type.__new__(cls, args[0]) - new._normal = args[1] if len(args) > 1 else u'' + new = six.text_type.__new__(cls, sequence) + new._normal = normal return new def __call__(self, *args): - """Return ``text`` joined by ``sequence`` and ``normal``.""" + """ + Return ``text`` joined by ``sequence`` and ``normal``. + + :raises TypeError: Not a string type + :rtype: str + :returns: Arguments wrapped in sequence and normal + """ # Jim Allman brings us this convenience of allowing existing # unicode strings to be joined as a call parameter to a formatting # string result, allowing nestation: @@ -224,7 +237,8 @@ class FormattingOtherString(six.text_type): r""" A Unicode string which doubles as a callable for another sequence when called. - This is used for the :meth:`~.Terminal.move_up`, ``down``, ``left``, and ``right()`` family of functions:: + This is used for the :meth:`~.Terminal.move_up`, ``down``, ``left``, and ``right()`` + family of functions:: >>> move_right = FormattingOtherString(term.cuf1, term.cuf) >>> print(repr(move_right)) @@ -235,24 +249,25 @@ class FormattingOtherString(six.text_type): u'\x1b[C' """ - def __new__(cls, *args): + def __new__(cls, direct, target): + # pylint: disable = missing-return-doc, missing-return-type-doc """ Class constructor accepting 2 positional arguments. :arg str direct: capability name for direct formatting, eg ``('x' + term.right)``. - :arg str callable: capability name for callable, eg ``('x' + term.right(99))``. + :arg str target: capability name for callable, eg ``('x' + term.right(99))``. """ - assert 2 == len(args), args - new = six.text_type.__new__(cls, args[0]) - new._callable = args[1] + new = six.text_type.__new__(cls, direct) + new._callable = target return new def __call__(self, *args): - """Return ``text`` by ``callable``.""" + """Return ``text`` by ``target``.""" if args: return self._callable(*args) return self + class NullCallableString(six.text_type): """ A dummy callable Unicode alternative to :class:`FormattingString`. @@ -343,6 +358,7 @@ def split_compound(compound): :arg str compound: a string that may contain compounds, separated by underline (``_``). :rtype: list + :returns: List of formating string segments """ merged_segs = [] # These occur only as prefixes, so they can always be merged: @@ -370,7 +386,7 @@ def resolve_capability(term, attr): # b'\xff' is returned, this must be decoded to u'\xff'. if not term.does_styling: return u'' - val = curses.tigetstr(term._sugar.get(attr, attr)) + val = curses.tigetstr(term._sugar.get(attr, attr)) # pylint: disable=protected-access return u'' if val is None else val.decode('latin1') @@ -390,6 +406,7 @@ def resolve_color(term, color): otherwise :class:`FormattingString`. :rtype: :class:`NullCallableString` or :class:`FormattingString` """ + # pylint: disable=protected-access if term.number_of_colors == 0: return NullCallableString() @@ -467,7 +484,8 @@ def resolve_attribute(term, attr): # and, for special terminals, such as 'screen', provide a Proxy # ParameterizingString for attributes they do not claim to support, # but actually do! (such as 'hpa' and 'vpa'). - proxy = get_proxy_string(term, term._sugar.get(attr, attr)) + proxy = get_proxy_string(term, + term._sugar.get(attr, attr)) # pylint: disable=protected-access if proxy is not None: return proxy diff --git a/blessed/keyboard.py b/blessed/keyboard.py index d1ff45a4..59ba8cf5 100644 --- a/blessed/keyboard.py +++ b/blessed/keyboard.py @@ -80,10 +80,9 @@ def get_curses_keycodes(): Return mapping of curses key-names paired by their keycode integer value. :rtype: dict - - Returns dictionary of (name, code) pairs for curses keyboard constant - values and their mnemonic name. Such as code ``260``, with the value of - its key-name identity, ``u'KEY_LEFT'``. + :returns: Dictionary of (name, code) pairs for curses keyboard constant + values and their mnemonic name. Such as code ``260``, with the value of + its key-name identity, ``u'KEY_LEFT'``. """ _keynames = [attr for attr in dir(curses) if attr.startswith('KEY_')] @@ -96,11 +95,12 @@ def get_keyboard_codes(): Return mapping of keycode integer values paired by their curses key-name. :rtype: dict + :returns: Dictionary of (code, name) pairs for curses keyboard constant + values and their mnemonic name. Such as key ``260``, with the value of + its identity, ``u'KEY_LEFT'``. - Returns dictionary of (code, name) pairs for curses keyboard constant - values and their mnemonic name. Such as key ``260``, with the value of - its identity, ``u'KEY_LEFT'``. These are derived from the attributes by - the same of the curses module, with the following exceptions: + These keys are derived from the attributes by the same of the curses module, + with the following exceptions: * ``KEY_DELETE`` in place of ``KEY_DC`` * ``KEY_INSERT`` in place of ``KEY_IC`` @@ -136,16 +136,17 @@ def _alternative_left_right(term): :arg blessed.Terminal term: :class:`~.Terminal` instance. :rtype: dict + :returns: Dictionary of sequences ``term._cuf1``, and ``term._cub1``, + valued as ``KEY_RIGHT``, ``KEY_LEFT`` (when appropriate). This function supports :func:`get_terminal_sequences` to discover the preferred input sequence for the left and right application keys. - Return dict of sequences ``term._cuf1``, and ``term._cub1``, - valued as ``KEY_RIGHT``, ``KEY_LEFT`` (when appropriate). It is - necessary to check the value of these sequences to ensure we do not + It is necessary to check the value of these sequences to ensure we do not use ``u' '`` and ``u'\b'`` for ``KEY_RIGHT`` and ``KEY_LEFT``, preferring their true application key sequence, instead. """ + # pylint: disable=protected-access keymap = dict() if term._cuf1 and term._cuf1 != u' ': keymap[term._cuf1] = curses.KEY_RIGHT @@ -209,6 +210,7 @@ def get_leading_prefixes(sequences): :arg iterable sequences :rtype: set + :return: Set of all string prefixes Given an iterable of strings, all textparts leading up to the final string is returned as a unique set. This function supports the @@ -225,17 +227,18 @@ def resolve_sequence(text, mapper, codes): r""" Return :class:`Keystroke` instance for given sequence ``text``. - The given ``text`` may extend beyond a matching sequence, such as - ``u\x1b[Dxxx`` returns a :class:`Keystroke` instance of attribute - :attr:`Keystroke.sequence` valued only ``u\x1b[D``. It is up to - determine that ``xxx`` remains unresolved. - - :arg text: string of characters received from terminal input stream. + :arg str text: string of characters received from terminal input stream. :arg OrderedDict mapper: unicode multibyte sequences, such as ``u'\x1b[D'`` paired by their integer value (260) :arg dict codes: a :type:`dict` of integer values (such as 260) paired by their mnemonic name, such as ``'KEY_LEFT'``. :rtype: Keystroke + :returns: Keystroke instance for the given sequence + + The given ``text`` may extend beyond a matching sequence, such as + ``u\x1b[Dxxx`` returns a :class:`Keystroke` instance of attribute + :attr:`Keystroke.sequence` valued only ``u\x1b[D``. It is up to + determine that ``xxx`` remains unresolved. """ for sequence, code in mapper.items(): if text.startswith(sequence): @@ -349,10 +352,10 @@ def _read_until(term, pattern, timeout): 'KP_8', 'KP_9') -_lastval = max(get_curses_keycodes().values()) +_LASTVAL = max(get_curses_keycodes().values()) for keycode_name in _CURSES_KEYCODE_ADDINS: - _lastval += 1 - globals()['KEY_' + keycode_name] = _lastval + _LASTVAL += 1 + globals()['KEY_' + keycode_name] = _LASTVAL #: In a perfect world, terminal emulators would always send exactly what #: the terminfo(5) capability database plans for them, accordingly by the @@ -375,7 +378,7 @@ def _read_until(term, pattern, timeout): (six.unichr(10), curses.KEY_ENTER), (six.unichr(13), curses.KEY_ENTER), (six.unichr(8), curses.KEY_BACKSPACE), - (six.unichr(9), KEY_TAB), # noqa + (six.unichr(9), KEY_TAB), # noqa # pylint: disable=undefined-variable (six.unichr(27), curses.KEY_EXIT), (six.unichr(127), curses.KEY_BACKSPACE), @@ -396,23 +399,23 @@ def _read_until(term, pattern, timeout): # # keypad, numlock on (u"\x1bOM", curses.KEY_ENTER), # noqa return - (u"\x1bOj", KEY_KP_MULTIPLY), # noqa * - (u"\x1bOk", KEY_KP_ADD), # noqa + - (u"\x1bOl", KEY_KP_SEPARATOR), # noqa , - (u"\x1bOm", KEY_KP_SUBTRACT), # noqa - - (u"\x1bOn", KEY_KP_DECIMAL), # noqa . - (u"\x1bOo", KEY_KP_DIVIDE), # noqa / - (u"\x1bOX", KEY_KP_EQUAL), # noqa = - (u"\x1bOp", KEY_KP_0), # noqa 0 - (u"\x1bOq", KEY_KP_1), # noqa 1 - (u"\x1bOr", KEY_KP_2), # noqa 2 - (u"\x1bOs", KEY_KP_3), # noqa 3 - (u"\x1bOt", KEY_KP_4), # noqa 4 - (u"\x1bOu", KEY_KP_5), # noqa 5 - (u"\x1bOv", KEY_KP_6), # noqa 6 - (u"\x1bOw", KEY_KP_7), # noqa 7 - (u"\x1bOx", KEY_KP_8), # noqa 8 - (u"\x1bOy", KEY_KP_9), # noqa 9 + (u"\x1bOj", KEY_KP_MULTIPLY), # noqa * # pylint: disable=undefined-variable + (u"\x1bOk", KEY_KP_ADD), # noqa + # pylint: disable=undefined-variable + (u"\x1bOl", KEY_KP_SEPARATOR), # noqa , # pylint: disable=undefined-variable + (u"\x1bOm", KEY_KP_SUBTRACT), # noqa - # pylint: disable=undefined-variable + (u"\x1bOn", KEY_KP_DECIMAL), # noqa . # pylint: disable=undefined-variable + (u"\x1bOo", KEY_KP_DIVIDE), # noqa / # pylint: disable=undefined-variable + (u"\x1bOX", KEY_KP_EQUAL), # noqa = # pylint: disable=undefined-variable + (u"\x1bOp", KEY_KP_0), # noqa 0 # pylint: disable=undefined-variable + (u"\x1bOq", KEY_KP_1), # noqa 1 # pylint: disable=undefined-variable + (u"\x1bOr", KEY_KP_2), # noqa 2 # pylint: disable=undefined-variable + (u"\x1bOs", KEY_KP_3), # noqa 3 # pylint: disable=undefined-variable + (u"\x1bOt", KEY_KP_4), # noqa 4 # pylint: disable=undefined-variable + (u"\x1bOu", KEY_KP_5), # noqa 5 # pylint: disable=undefined-variable + (u"\x1bOv", KEY_KP_6), # noqa 6 # pylint: disable=undefined-variable + (u"\x1bOw", KEY_KP_7), # noqa 7 # pylint: disable=undefined-variable + (u"\x1bOx", KEY_KP_8), # noqa 8 # pylint: disable=undefined-variable + (u"\x1bOy", KEY_KP_9), # noqa 9 # pylint: disable=undefined-variable # keypad, numlock off (u"\x1b[1~", curses.KEY_FIND), # find diff --git a/blessed/sequences.py b/blessed/sequences.py index dd15040c..72d0b53a 100644 --- a/blessed/sequences.py +++ b/blessed/sequences.py @@ -39,11 +39,13 @@ def __repr__(self): @property def named_pattern(self): + """Regular expression pattern for capability with named group.""" # pylint: disable=redundant-keyword-arg return '(?P<{self.name}>{self.pattern})'.format(self=self) @property def re_compiled(self): + """Compiled regular expression pattern for capability.""" if self._re_compiled is None: self._re_compiled = re.compile(self.pattern) return self._re_compiled @@ -97,9 +99,10 @@ def build(cls, name, capability, attribute, nparams=0, capability to build for. When ``nparams`` is non-zero, it must be a callable unicode string (such as the result from ``getattr(term, 'bold')``. - :arg attribute: The terminfo(5) capability name by which this + :arg str attribute: The terminfo(5) capability name by which this pattern is known. :arg int nparams: number of positional arguments for callable. + :arg int numeric: Value to substitute into capability to when generating pattern :arg bool match_grouped: If the numeric pattern should be grouped, ``(\d+)`` when ``True``, ``\d+`` default. :arg bool match_any: When keyword argument ``nparams`` is given, @@ -109,6 +112,8 @@ def build(cls, name, capability, attribute, nparams=0, pattern ``(\d+)`` in builder. :arg bool match_optional: When ``True``, building of numeric patterns containing ``(\d+)`` will be built as optional, ``(\d+)?``. + :rtype: blessed.sequences.Termcap + :returns: Terminal capability instance for given capability definition """ _numeric_regex = r'\d+' if match_grouped: @@ -152,6 +157,10 @@ def _wrap_chunks(self, chunks): """ Sequence-aware variant of :meth:`textwrap.TextWrapper._wrap_chunks`. + :raises ValueError: ``self.width`` is not a positive integer + :rtype: list + :returns: text chunks adjusted for width + This simply ensures that word boundaries are not broken mid-sequence, as standard python textwrap would incorrectly determine the length of a string containing sequences, and may also break consider sequences part of a "word" that may be broken by hyphen (``-``), where @@ -251,10 +260,11 @@ class Sequence(six.text_type): """ def __new__(cls, sequence_text, term): + # pylint: disable = missing-return-doc, missing-return-type-doc """ Class constructor. - :arg sequence_text: A string that may contain sequences. + :arg str sequence_text: A string that may contain sequences. :arg blessed.Terminal term: :class:`~.Terminal` instance. """ new = six.text_type.__new__(cls, sequence_text) @@ -265,10 +275,10 @@ def ljust(self, width, fillchar=u' '): """ Return string containing sequences, left-adjusted. - :arg int width: Total width given to right-adjust ``text``. If + :arg int width: Total width given to left-adjust ``text``. If unspecified, the width of the attached terminal is used (default). :arg str fillchar: String for padding right-of ``text``. - :returns: String of ``text``, right-aligned by ``width``. + :returns: String of ``text``, left-aligned by ``width``. :rtype: str """ rightside = fillchar * int( @@ -326,8 +336,9 @@ def length(self): # we require ur"" for the docstring, but it is not supported by all # python versions. - if length.__doc__ is not None: - length.__doc__ += ( + # This may no longer be needed + if length.__doc__ is not None: # pylint: disable=no-member + length.__doc__ += ( # pylint: disable=no-member u""" For example: @@ -345,10 +356,11 @@ def length(self): def strip(self, chars=None): """ - Return string of sequences, leading, and trailing whitespace removed. + Return string of sequences, leading and trailing whitespace removed. :arg str chars: Remove characters in chars instead of whitespace. :rtype: str + :returns: string of sequences with leading and trailing whitespace removed. """ return self.strip_seqs().strip(chars) @@ -358,6 +370,7 @@ def lstrip(self, chars=None): :arg str chars: Remove characters in chars instead of whitespace. :rtype: str + :returns: string of sequences with leading removed. """ return self.strip_seqs().lstrip(chars) @@ -367,6 +380,7 @@ def rstrip(self, chars=None): :arg str chars: Remove characters in chars instead of whitespace. :rtype: str + :returns: string of sequences with trailing removed. """ return self.strip_seqs().rstrip(chars) @@ -375,6 +389,7 @@ def strip_seqs(self): Return ``text`` stripped of only its terminal sequences. :rtype: str + :returns: Text with terminal sequences removed """ gen = iter_parse(self._term, self.padd()) return u''.join(text for text, cap in gen if not cap) @@ -384,6 +399,7 @@ def padd(self): Return non-destructive horizontal movement as destructive spacing. :rtype: str + :returns: Text adjusted for horizontal movement """ outp = '' for text, cap in iter_parse(self._term, self): @@ -409,7 +425,7 @@ def iter_parse(term, text): :class:`str` of length 1. Otherwise, ``text`` is a full matching sequence of given capability. """ - for match in re.finditer(term._caps_compiled_any, text): + for match in re.finditer(term._caps_compiled_any, text): # pylint: disable=protected-access name = match.lastgroup value = match.group(name) if name == 'MISMATCH': @@ -423,6 +439,7 @@ def measure_length(text, term): .. deprecated:: 1.12.0. :rtype: int + :returns: Length of the first sequence in the string """ try: text, capability = next(iter_parse(term, text)) diff --git a/blessed/terminal.py b/blessed/terminal.py index e7fa1141..c1889458 100644 --- a/blessed/terminal.py +++ b/blessed/terminal.py @@ -1,4 +1,5 @@ # encoding: utf-8 +# pylint: disable=too-many-lines """Module containing :class:`Terminal`, the primary API entry point.""" # std imports import os @@ -27,9 +28,9 @@ from .colorspace import RGB_256TABLE from .formatters import (COLORS, FormattingString, - FormattingOtherString, NullCallableString, ParameterizingString, + FormattingOtherString, resolve_attribute, resolve_capability) from ._capabilities import CAPABILITY_DATABASE, CAPABILITIES_ADDITIVES, CAPABILITIES_RAW_MIXIN @@ -156,39 +157,22 @@ def __init__(self, kind=None, stream=None, force_styling=False): global _CUR_TERM self.errors = ['parameters: kind=%r, stream=%r, force_styling=%r' % (kind, stream, force_styling)] - self._keyboard_fd = None - - # Default stream is stdout, keyboard valid as stdin only when - # output stream is stdout or stderr and is a tty. - if stream is None: - stream = sys.__stdout__ - if stream in (sys.__stdout__, sys.__stderr__): - self._keyboard_fd = sys.__stdin__.fileno() - + self._normal = None # cache normal attr, preventing recursive lookups # we assume our input stream to be line-buffered until either the - # cbreak of raw context manager methods are entered with an - # attached tty. + # cbreak of raw context manager methods are entered with an attached tty. self._line_buffered = True - stream_fd = None + self._stream = stream + self._keyboard_fd = None + self._init_descriptor = None self._is_a_tty = False - if not hasattr(stream, 'fileno'): - self.errors.append('stream has no fileno method') - elif not callable(stream.fileno): - self.errors.append('stream.fileno is not callable') + self.__init__streams() + + if platform.system() == 'Windows' and self._init_descriptor is not None: + self._kind = kind or curses.get_term(self._init_descriptor) else: - try: - stream_fd = stream.fileno() - except ValueError as err: - # The stream is not a file, such as the case of StringIO, or, when it has been - # "detached", such as might be the case of stdout in some test scenarios. - self.errors.append('Unable to determine stream file descriptor: %s' % err) - else: - self._is_a_tty = os.isatty(stream_fd) - if not self._is_a_tty: - self.errors.append('stream not a TTY') + self._kind = kind or os.environ.get('TERM', 'dumb') or 'dumb' - self._stream = stream self._does_styling = False if force_styling: self._does_styling = True @@ -198,26 +182,6 @@ def __init__(self, kind=None, stream=None, force_styling=False): else: self._does_styling = True - # _keyboard_fd only non-None if both stdin and stdout is a tty. - self._keyboard_fd = (self._keyboard_fd - if self._keyboard_fd is not None and - self.is_a_tty and os.isatty(self._keyboard_fd) - else None) - self._normal = None # cache normal attr, preventing recursive lookups - - # The descriptor to direct terminal initialization sequences to. - self._init_descriptor = stream_fd - if stream_fd is None: - try: - self._init_descriptor = sys.__stdout__.fileno() - except ValueError as err: - self.errors.append('Unable to determine __stdout__ file descriptor: %s' % err) - - if platform.system() == 'Windows' and self._init_descriptor is not None: - self._kind = kind or curses.get_term(self._init_descriptor) - else: - self._kind = kind or os.environ.get('TERM', 'dumb') or 'dumb' - if self.does_styling: # Initialize curses (call setupterm), so things like tigetstr() work. try: @@ -248,13 +212,53 @@ def __init__(self, kind=None, stream=None, force_styling=False): self.__init__capabilities() self.__init__keycodes() + def __init__streams(self): + stream_fd = None + + # Default stream is stdout + if self._stream is None: + self._stream = sys.__stdout__ + + if not hasattr(self._stream, 'fileno'): + self.errors.append('stream has no fileno method') + elif not callable(self._stream.fileno): + self.errors.append('stream.fileno is not callable') + else: + try: + stream_fd = self._stream.fileno() + except ValueError as err: + # The stream is not a file, such as the case of StringIO, or, when it has been + # "detached", such as might be the case of stdout in some test scenarios. + self.errors.append('Unable to determine stream file descriptor: %s' % err) + else: + self._is_a_tty = os.isatty(stream_fd) + if not self._is_a_tty: + self.errors.append('stream not a TTY') + + # Keyboard valid as stdin only when output stream is stdout or stderr and is a tty. + if self._stream in (sys.__stdout__, sys.__stderr__): + self._keyboard_fd = sys.__stdin__.fileno() + + # _keyboard_fd only non-None if both stdin and stdout is a tty. + self._keyboard_fd = (self._keyboard_fd + if self._keyboard_fd is not None and + self.is_a_tty and os.isatty(self._keyboard_fd) + else None) + + # The descriptor to direct terminal initialization sequences to. + self._init_descriptor = stream_fd + if stream_fd is None: + try: + self._init_descriptor = sys.__stdout__.fileno() + except ValueError as err: + self.errors.append('Unable to determine __stdout__ file descriptor: %s' % err) + def __init__color_capabilities(self): self._color_distance_algorithm = 'cie2000' if not self.does_styling: self.number_of_colors = 0 elif platform.system() == 'Windows' or ( - os.environ.get('COLORTERM') in ('truecolor', '24bit') - ): + os.environ.get('COLORTERM') in ('truecolor', '24bit')): self.number_of_colors = 1 << 24 else: self.number_of_colors = max(0, curses.tigetnum('colors') or -1) @@ -429,6 +433,7 @@ def _winsize(fd): :arg int fd: file descriptor queries for its window size. :raises IOError: the file descriptor ``fd`` is not a terminal. :rtype: WINSZ + :returns: named tuple describing size of the terminal WINSZ is a :class:`collections.namedtuple` instance, whose structure directly maps to the return value of the :const:`termios.TIOCGWINSZ` @@ -440,6 +445,7 @@ def _winsize(fd): - ``ws_ypixel``: height of terminal by pixels (not accurate). """ if HAS_TTY: + # pylint: disable=protected-access data = fcntl.ioctl(fd, termios.TIOCGWINSZ, WINSZ._BUF) return WINSZ(*struct.unpack(WINSZ._FMT, data)) return WINSZ(ws_row=25, ws_col=80, ws_xpixel=0, ws_ypixel=0) @@ -455,6 +461,7 @@ def _height_and_width(self): is the default text mode of IBM PC compatibles. :rtype: WINSZ + :returns: Named tuple specifying the terminal size WINSZ is a :class:`collections.namedtuple` instance, whose structure directly maps to the return value of the :const:`termios.TIOCGWINSZ` @@ -469,7 +476,7 @@ def _height_and_width(self): try: if fd is not None: return self._winsize(fd) - except (IOError, OSError, ValueError, TypeError): + except (IOError, OSError, ValueError, TypeError): # pylint: disable=overlapping-except pass return WINSZ(ws_row=int(os.getenv('LINES', '25')), @@ -579,7 +586,7 @@ def get_location(self, timeout=None): try: if self._line_buffered: ctx = self.cbreak() - ctx.__enter__() + ctx.__enter__() # pylint: disable=no-member # emit the 'query cursor position' sequence, self.stream.write(query_str) @@ -615,7 +622,7 @@ def get_location(self, timeout=None): finally: if ctx is not None: - ctx.__exit__(None, None, None) + ctx.__exit__(None, None, None) # pylint: disable=no-member # We chose to return an illegal value rather than an exception, # favoring that users author function filters, such as max(0, y), @@ -673,6 +680,7 @@ def move_xy(self, x, y): :arg int x: horizontal position. :arg int y: vertical position. :rtype: ParameterizingString + :returns: Callable string that moves the cursor to the given coordinates """ # this is just a convenience alias to the built-in, but hidden 'move' # attribute -- we encourage folks to use only (x, y) positional @@ -687,6 +695,7 @@ def move_yx(self, y, x): :arg int x: horizontal position. :arg int y: vertical position. :rtype: ParameterizingString + :returns: Callable string that moves the cursor to the given coordinates """ return self.move(y, x) @@ -715,7 +724,6 @@ def color(self): """ A callable string that sets the foreground color. - :arg int num: The foreground color index. :rtype: ParameterizingString The capability is unparameterized until called and passed a number, at which point it @@ -731,6 +739,18 @@ def color(self): self.normal, 'color') def color_rgb(self, red, green, blue): + """ + Provides callable formatting string to set foreground color to the specified RGB color. + + :arg int red: RGB value of Red. + :arg int green: RGB value of Green. + :arg int blue: RGB value of Blue. + :rtype: FormattingString + :returns: Callable string that sets the foreground color + + If the terminal does not support RGB color, the nearest supported + color will be determined using :py:attr:`color_distance_algorithm`. + """ if self.number_of_colors == 1 << 24: # "truecolor" 24-bit fmt_attr = u'\x1b[38;2;{0};{1};{2}m'.format(red, green, blue) @@ -745,7 +765,6 @@ def on_color(self): """ A callable capability that sets the background color. - :arg int num: The background color index. :rtype: ParameterizingString """ if not self.does_styling: @@ -754,6 +773,18 @@ def on_color(self): self.normal, 'on_color') def on_color_rgb(self, red, green, blue): + """ + Provides callable formatting string to set background color to the specified RGB color. + + :arg int red: RGB value of Red. + :arg int green: RGB value of Green. + :arg int blue: RGB value of Blue. + :rtype: FormattingString + :returns: Callable string that sets the foreground color + + If the terminal does not support RGB color, the nearest supported + color will be determined using :py:attr:`color_distance_algorithm`. + """ if self.number_of_colors == 1 << 24: fmt_attr = u'\x1b[48;2;{0};{1};{2}m'.format(red, green, blue) return FormattingString(fmt_attr, self.normal) @@ -769,6 +800,7 @@ def rgb_downconvert(self, red, green, blue): :arg int green: RGB value of Green (0-255). :arg int blue: RGB value of Blue (0-255). :rtype: int + :returns: Color code of downconverted RGB color """ # Though pre-computing all 1 << 24 options is memory-intensive, a pre-computed # "k-d tree" of 256 (x,y,z) vectors of a colorspace in 3 dimensions, such as a @@ -880,6 +912,7 @@ def ljust(self, text, width=None, fillchar=u' '): unspecified, the whole width of the terminal is filled. :arg str fillchar: String for padding the right of ``text`` :rtype: str + :returns: String of ``text``, left-aligned by ``width``. """ # Left justification is different from left alignment, but we continue # the vocabulary error of the str method for polymorphism. @@ -896,6 +929,7 @@ def rjust(self, text, width=None, fillchar=u' '): unspecified, the whole width of the terminal is used. :arg str fillchar: String for padding the left of ``text`` :rtype: str + :returns: String of ``text``, right-aligned by ``width``. """ if width is None: width = self.width @@ -910,6 +944,7 @@ def center(self, text, width=None, fillchar=u' '): unspecified, the whole width of the terminal is used. :arg str fillchar: String for padding the left and right of ``text`` :rtype: str + :returns: String of ``text``, centered by ``width`` """ if width is None: width = self.width @@ -942,6 +977,7 @@ def strip(self, text, chars=None): Return ``text`` without sequences and leading or trailing whitespace. :rtype: str + :returns: Text with leading and trailing whitespace removed >>> term.strip(u' \x1b[0;3m xyz ') u'xyz' @@ -953,6 +989,7 @@ def rstrip(self, text, chars=None): Return ``text`` without terminal sequences or trailing whitespace. :rtype: str + :returns: Text with terminal sequences and trailing whitespace removed >>> term.rstrip(u' \x1b[0;3m xyz ') u' xyz' @@ -964,6 +1001,7 @@ def lstrip(self, text, chars=None): Return ``text`` without terminal sequences or leading whitespace. :rtype: str + :returns: Text with terminal sequences and leading whitespace removed >>> term.lstrip(u' \x1b[0;3m xyz ') u'xyz ' @@ -975,6 +1013,7 @@ def strip_seqs(self, text): Return ``text`` stripped of only its terminal sequences. :rtype: str + :returns: Text with terminal sequences removed >>> term.strip_seqs(u'\x1b[0;3mxyz') u'xyz' @@ -991,8 +1030,10 @@ def split_seqs(self, text, **kwds): r""" Return ``text`` split by individual character elements and sequences. + :arg str text: String containing sequences :arg kwds: remaining keyword arguments for :func:`re.split`. :rtype: list[str] + :returns: List of sequences and individual characters >>> term.split_seqs(term.underline(u'xyz')) ['\x1b[4m', 'x', 'y', 'z', '\x1b(B', '\x1b[m'] @@ -1010,7 +1051,9 @@ def wrap(self, text, width=None, **kwargs): :func:`string.expandtabs`. :arg int width: Unlike :func:`textwrap.wrap`, ``width`` will default to the width of the attached terminal. + :arg kwargs: See :py:class:`textwrap.TextWrapper` :rtype: list + :returns: List of wrapped lines See :class:`textwrap.TextWrapper` for keyword arguments that can customize wrapping behaviour. @@ -1049,7 +1092,7 @@ def ungetch(self, text): """ Buffer input data to be discovered by next call to :meth:`~.inkey`. - :arg str ucs: String to be buffered as keyboard input. + :arg str text: String to be buffered as keyboard input. """ self._keyboard_buf.extendleft(text) diff --git a/blessed/win_terminal.py b/blessed/win_terminal.py index 68cdabe2..1bd04a11 100644 --- a/blessed/win_terminal.py +++ b/blessed/win_terminal.py @@ -43,7 +43,7 @@ def getch(self): rtn += msvcrt.getwch() return rtn - def kbhit(self, timeout=None, **_kwargs): + def kbhit(self, timeout=None): """ Return whether a keypress has been detected on the keyboard. @@ -78,6 +78,7 @@ def _winsize(fd): :arg int fd: file descriptor queries for its window size. :rtype: WINSZ + :returns: named tuple describing size of the terminal WINSZ is a :class:`collections.namedtuple` instance, whose structure directly maps to the return value of the :const:`termios.TIOCGWINSZ` diff --git a/tox.ini b/tox.ini index fb2b6282..856281fc 100644 --- a/tox.ini +++ b/tox.ini @@ -67,6 +67,14 @@ max-line-length = 100 exclude = .tox,build deps = flake8==3.7.9 +[pydocstyle] +ignore = D101, # Missing docstring in public class + D105, # Missing docstring in magic method + D203, # 1 blank line required before class docstring + D204, # 1 blank line required after class docstring + D212, # Multi-line docstring summary should start at the first line + D401 # First line should be in imperative mood + [doc8] max-line-length = 100 @@ -172,9 +180,8 @@ commands = {envbindir}/flake8 --ignore=W504,F811,F401 tests/ deps = pydocstyle==3.0.0 restructuredtext_lint doc8 -commands = {envbindir}/pydocstyle --source --explain \ - --ignore=D101,D212,D203,D204,D401 \ - {toxinidir}/blessed + pygments +commands = {envbindir}/pydocstyle --source --explain {toxinidir}/blessed {envbindir}/rst-lint README.rst {envbindir}/doc8 --ignore-path docs/_build --ignore D000 docs