Skip to content

Commit

Permalink
Bugfix for term.split_seqs('term.right(3)')
Browse files Browse the repository at this point in the history
In version 1.18.0 and earlier,

>> term.split_seqs(term.move_right(333) + 'xyz', maxsplit=1)
['\x1b[333C', '333', 'xyz']

This is a bug, it duplicates the matched parameter, this is
now corrected:

['\x1b[3C', 'xyz']

Previously, we documented "same arguments as "re.split", so
we must also implement maxsplit and flags.

Also,
- fix flake8 linting of tests by moving fixtures to conftest.py,
  fixes "unused" or "re-definition from import" errors.
- version stamp blessed/__init__.py like a codegen step i guess
- remove run_codecov.py, its been fixed upstream
  codecov/codecov-python#158 (comment)
  • Loading branch information
jquast committed Mar 6, 2021
1 parent 33f3660 commit 0224922
Show file tree
Hide file tree
Showing 15 changed files with 142 additions and 94 deletions.
2 changes: 1 addition & 1 deletion blessed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
'support due to http://bugs.python.org/issue10570.')

__all__ = ('Terminal',)
__version__ = '1.18.0'
__version__ = "1.18.1"
22 changes: 19 additions & 3 deletions blessed/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1106,20 +1106,36 @@ def strip_seqs(self, text):
"""
return Sequence(text, self).strip_seqs()

def split_seqs(self, text, **kwds):
def split_seqs(self, text, maxsplit=0, flags=0):
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`.
:arg int maxsplit: When maxsplit is nonzero, at most maxsplit splits
occur, and the remainder of the string is returned as the final element
of the list (same meaning is argument for :func:`re.split`).
:arg int flags: regex flags, combined integer flag attributes documented
beginning with flag attribute :attr:`re.A` (same meaning is argument
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']
>>> term.split_seqs(term.underline(u'xyz'), 1)
['\x1b[4m', r'xyz\x1b(B\x1b[m']
"""
pattern = self._caps_unnamed_any
return list(filter(None, re.split(pattern, text, **kwds)))
result = []
for idx, match in enumerate(re.finditer(pattern, text, flags)):
result.append(match.group())
if maxsplit and idx == maxsplit:
remaining = text[match.end():]
if remaining:
result[-1] += remaining
break
return result

def wrap(self, text, width=None, **kwargs):
"""
Expand Down
2 changes: 1 addition & 1 deletion blessed/terminal.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class Terminal:
def rstrip(self, text: str, chars: Optional[str] = ...) -> str: ...
def lstrip(self, text: str, chars: Optional[str] = ...) -> str: ...
def strip_seqs(self, text: str) -> str: ...
def split_seqs(self, text: str, **kwds: Any) -> List[str]: ...
def split_seqs(self, text: str, maxsplit: int, flags: int) -> List[str]: ...
def wrap(
self, text: str, width: Optional[int] = ..., **kwargs: Any
) -> List[str]: ...
Expand Down
2 changes: 2 additions & 0 deletions docs/history.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Version History
===============
1.18
* bugfix: :meth:`~Terminal.split_seqs` for some sequences
like ``term.move_left(3)``, :ghissue:`197`.
* introduced: type annotations, :ghissue:`192` by :ghuser:`dlax`.
* bugfix: do not fail when ``sys.stdin`` is unset, :ghissue:`195` by
:ghuser:`Olen`
Expand Down
47 changes: 0 additions & 47 deletions run_codecov.py

This file was deleted.

31 changes: 1 addition & 30 deletions tests/accessories.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
"""Accessories for automated py.test runner."""
# standard imports
from __future__ import print_function, with_statement

# std imports
Expand Down Expand Up @@ -40,15 +39,6 @@
TestTerminal = functools.partial(Terminal, kind=test_kind) # type: Callable[..., Terminal]
SEND_SEMAPHORE = SEMAPHORE = b'SEMAPHORE\n'
RECV_SEMAPHORE = b'SEMAPHORE\r\n'
many_lines_params = [40, 80]
# we must test a '1' column for conditional in _handle_long_word
many_columns_params = [1, 10]

if os.environ.get('TEST_QUICK'):
many_lines_params = [80, ]
many_columns_params = [25, ]

all_terms_params = 'xterm screen ansi vt220 rxvt cons25 linux'.split()

if os.environ.get('TEST_FULL'):
try:
Expand Down Expand Up @@ -164,8 +154,7 @@ def __call__(self, *args, **kwargs):
assert os.WEXITSTATUS(status) == 0


def read_until_semaphore(fd, semaphore=RECV_SEMAPHORE,
encoding='utf8', timeout=10):
def read_until_semaphore(fd, semaphore=RECV_SEMAPHORE, encoding='utf8'):
"""
Read file descriptor ``fd`` until ``semaphore`` is found.
Expand Down Expand Up @@ -254,21 +243,3 @@ def unicode_parm(cap, *parms):
if val:
return val.decode('latin1')
return u''


@pytest.fixture(params=all_terms_params)
def all_terms(request):
"""Common kind values for all kinds of terminals."""
return request.param


@pytest.fixture(params=many_lines_params)
def many_lines(request):
"""Various number of lines for screen height."""
return request.param


@pytest.fixture(params=many_columns_params)
def many_columns(request):
"""Various number of columns for screen width."""
return request.param
32 changes: 32 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# std imports
import os

# 3rd party
import pytest

all_terms_params = 'xterm screen ansi vt220 rxvt cons25 linux'.split()
many_lines_params = [40, 80]
# we must test a '1' column for conditional in _handle_long_word
many_columns_params = [1, 10]

if os.environ.get('TEST_QUICK'):
many_lines_params = [80, ]
many_columns_params = [25, ]


@pytest.fixture(params=all_terms_params)
def all_terms(request):
"""Common kind values for all kinds of terminals."""
return request.param


@pytest.fixture(params=many_lines_params)
def many_lines(request):
"""Various number of lines for screen height."""
return request.param


@pytest.fixture(params=many_columns_params)
def many_columns(request):
"""Various number of columns for screen width."""
return request.param
2 changes: 1 addition & 1 deletion tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from six.moves import reload_module

# local
from .accessories import TestTerminal, all_terms, unicode_cap, as_subprocess
from .accessories import TestTerminal, unicode_cap, as_subprocess


def test_export_only_Terminal():
Expand Down
2 changes: 1 addition & 1 deletion tests/test_keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import pytest

# local
from .accessories import TestTerminal, all_terms, as_subprocess
from .accessories import TestTerminal, as_subprocess

if platform.system() != 'Windows':
import curses
Expand Down
4 changes: 2 additions & 2 deletions tests/test_length_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import six
import pytest

from .accessories import ( # isort:skip
TestTerminal, as_subprocess, all_terms, many_lines, many_columns)
# local
from .accessories import TestTerminal, as_subprocess

if platform.system() != 'Windows':
import fcntl
Expand Down
52 changes: 50 additions & 2 deletions tests/test_sequences.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pytest

# local
from .accessories import TestTerminal, all_terms, unicode_cap, unicode_parm, as_subprocess
from .accessories import TestTerminal, unicode_cap, unicode_parm, as_subprocess


@pytest.mark.skipif(platform.system() == 'Windows', reason="requires real tty")
Expand Down Expand Up @@ -543,9 +543,57 @@ def child(kind):
result = list(term.split_seqs(given_text))
assert result == expected

child(all_terms)


def test_split_seqs_maxsplit1(all_terms):
"""Test Terminal.split_seqs with maxsplit=1."""
@as_subprocess
def child(kind):
from blessed import Terminal
term = Terminal(kind)

if term.bold:
given_text = term.bold + 'bbq'
expected = [term.bold, 'b', 'b', 'q']
expected = [term.bold, 'bbq']
result = list(term.split_seqs(given_text, 1))
assert result == expected

child(all_terms)


def test_split_seqs_term_right(all_terms):
"""Test Terminal.split_seqs with parameterized sequence"""
@as_subprocess
def child(kind):
from blessed import Terminal
term = Terminal(kind)

if term.move_up:
given_text = 'XY' + term.move_right + 'VK'
expected = ['X', 'Y', term.move_right, 'V', 'K']
result = list(term.split_seqs(given_text))
assert result == expected

child(all_terms)


def test_split_seqs_maxsplit3_and_term_right(all_terms):
"""Test Terminal.split_seqs with parameterized sequence."""
@as_subprocess
def child(kind):
from blessed import Terminal
term = Terminal(kind)

if term.move_right(32):
given_text = 'PQ' + term.move_right(32) + 'RS'
expected = ['P', 'Q', term.move_right(32), 'RS']
result = list(term.split_seqs(given_text, 3))
assert result == expected

if term.move_up(45):
given_text = 'XY' + term.move_up(45) + 'VK'
expected = ['X', 'Y', term.move_up(45), 'V', 'K']
result = list(term.split_seqs(given_text))
assert result == expected

Expand Down
2 changes: 1 addition & 1 deletion tests/test_wrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pytest

# local
from .accessories import TestTerminal, many_columns, as_subprocess
from .accessories import TestTerminal, as_subprocess

TEXTWRAP_KEYWORD_COMBINATIONS = [
dict(break_long_words=False,
Expand Down
10 changes: 6 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[tox]
envlist = about
stamp
autopep8
docformatter
isort
Expand Down Expand Up @@ -152,6 +153,9 @@ commands = python {toxinidir}/bin/display-sighandlers.py
python {toxinidir}/bin/display-terminalinfo.py
python {toxinidir}/bin/display-fpathconf.py

[testenv:stamp]
commands = python {toxinidir}/version.py

[testenv:autopep8]
deps = autopep8==1.4.4
commands =
Expand Down Expand Up @@ -222,10 +226,8 @@ commands = {envbindir}/sphinx-build -v -W -d {toxinidir}/docs/_build/doctrees -b
[testenv:codecov]
basepython = python{env:TOXPYTHON:{env:TRAVIS_PYTHON_VERSION:3.8}}
passenv = TOXENV CI TRAVIS TRAVIS_* CODECOV_*
deps = codecov>=1.4.0
# commands = codecov -e TOXENV
# Workaround for https://github.com/codecov/codecov-python/issues/158
commands = {envpython} run_codecov.py -e TOXENV
deps = codecov>=2.1
commands = codecov -e TOXENV

[testenv:publish_static]
# Synchronize the artifacts in docs/_static/ with https://dxtz6bzwq9sxx.cloudfront.net/
Expand Down
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version": "1.18.0"}
{"version": "1.18.1"}
24 changes: 24 additions & 0 deletions version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
# std imports
import os
import re
import json


def main():
# I don't know why, we maintain __version__ in blessed, because that's
# how it was done a long time ago before pip, anyway we do basic
# code generation, version.json -> __init__.py
fpath_json = os.path.join(os.path.dirname(__file__), 'version.json')
version = json.load(open(fpath_json, 'r'))['version']
fpath_py = os.path.join(os.path.dirname(__file__), 'blessed', '__init__.py')
prev_text = open(fpath_py, 'r').read()
next_text = re.sub(r"(__version__ = )(.*)$", r'\1"{0}"'.format(version),
prev_text, flags=re.MULTILINE)
if prev_text != next_text:
print('Updating blessed.__version__ to {}'.format(version))
open(fpath_py, 'w').write(next_text)


if __name__ == '__main__':
main()

0 comments on commit 0224922

Please sign in to comment.