diff options
author | Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> | 2018-09-19 18:49:53 +0900 |
---|---|---|
committer | Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> | 2018-09-19 18:56:17 +0900 |
commit | 7ba047b47f34715b57426259be48866283860a14 (patch) | |
tree | 0963f993742f9a9f433a9f913baf0526be0a043d | |
parent | 9c1847b95aa950abc639b216d4f2686b42813601 (diff) | |
download | buildstream-7ba047b47f34715b57426259be48866283860a14.tar.gz |
_frontend/status.py: Completely remove the blessings dependency from BuildStream
This actually improves reliability of the status bar because we
now disable it completely in the case that not all of the terminal
escape sequences are supported on the given terminal.
This replaces the few functions we were using, to move the cursor
up one line, move it to the beginning of the line, and to clear a
line, with low level functions provided by the curses module in
the standard library.
This change makes it easier for downstream distro package maintainers
to package BuildStream, particularly on Fedora.
Asides from changing _frontend/status.py, this commit includes the
following changes:
* _frontend/app.py: Use python isatty() function to determine if
we are connected to a tty, instead of relying
on blessings.
* setup.py: Remove the dependency on blessings.
-rw-r--r-- | buildstream/_frontend/app.py | 2 | ||||
-rw-r--r-- | buildstream/_frontend/status.py | 82 | ||||
-rwxr-xr-x | setup.py | 1 |
3 files changed, 77 insertions, 8 deletions
diff --git a/buildstream/_frontend/app.py b/buildstream/_frontend/app.py index be9aae4e5..3283b629f 100644 --- a/buildstream/_frontend/app.py +++ b/buildstream/_frontend/app.py @@ -93,7 +93,7 @@ class App(): # # Earily initialization # - is_a_tty = Terminal().is_a_tty + is_a_tty = sys.stdout.isatty() and sys.stderr.isatty() # Enable interactive mode if we're attached to a tty if main_options['no_interactive']: diff --git a/buildstream/_frontend/status.py b/buildstream/_frontend/status.py index 51b28d9cf..fd1a5acf1 100644 --- a/buildstream/_frontend/status.py +++ b/buildstream/_frontend/status.py @@ -16,8 +16,10 @@ # # Authors: # Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> +import os +import sys import click -from blessings import Terminal +import curses # Import a widget internal for formatting time codes from .widget import TimeCode @@ -43,6 +45,13 @@ from .._scheduler import ElementJob # class Status(): + # Table of the terminal capabilities we require and use + _TERM_CAPABILITIES = { + 'move_up': 'cuu1', + 'move_x': 'hpa', + 'clear_eol': 'el' + } + def __init__(self, context, content_profile, format_profile, success_profile, error_profile, @@ -56,7 +65,6 @@ class Status(): self._stream = stream self._jobs = [] self._last_lines = 0 # Number of status lines we last printed to console - self._term = Terminal() self._spacing = 1 self._colors = colors self._header = _StatusHeader(context, @@ -69,6 +77,7 @@ class Status(): self._alloc_columns = None self._line_length = 0 self._need_alloc = True + self._term_caps = self._init_terminal() # add_job() # @@ -121,7 +130,7 @@ class Status(): # def clear(self): - if not self._term.does_styling: + if not self._term_caps: return for _ in range(self._last_lines): @@ -138,7 +147,7 @@ class Status(): # not necessary to call clear(). def render(self): - if not self._term.does_styling: + if not self._term_caps: return elapsed = self._stream.elapsed_time @@ -185,6 +194,55 @@ class Status(): ################################################### # Private Methods # ################################################### + + # _init_terminal() + # + # Initialize the terminal and return the resolved terminal + # capabilities dictionary. + # + # Returns: + # (dict|None): The resolved terminal capabilities dictionary, + # or None if the terminal does not support all + # of the required capabilities. + # + def _init_terminal(self): + + # We need both output streams to be connected to a terminal + if not (sys.stdout.isatty() and sys.stderr.isatty()): + return None + + # Initialized terminal, curses might decide it doesnt + # support this terminal + try: + curses.setupterm(os.environ.get('TERM', 'dumb')) + except curses.error: + return None + + term_caps = {} + + # Resolve the string capabilities we need for the capability + # names we need. + # + for capname, capval in self._TERM_CAPABILITIES.items(): + code = curses.tigetstr(capval) + + # If any of the required capabilities resolve empty strings or None, + # then we don't have the capabilities we need for a status bar on + # this terminal. + if not code: + return None + + # Decode sequences as latin1, as they are always 8-bit bytes, + # so when b'\xff' is returned, this must be decoded to u'\xff'. + # + # This technique is employed by the python blessings library + # as well, and should provide better compatibility with most + # terminals. + # + term_caps[capname] = code.decode('latin1') + + return term_caps + def _check_term_width(self): term_width, _ = click.get_terminal_size() if self._term_width != term_width: @@ -192,12 +250,24 @@ class Status(): self._need_alloc = True def _move_up(self): + assert self._term_caps is not None + # Explicitly move to beginning of line, fixes things up # when there was a ^C or ^Z printed to the terminal. - click.echo(self._term.move_x(0) + self._term.move_up, nl=False, err=True) + move_x = curses.tparm(self._term_caps['move_x'].encode('latin1'), 0) + move_x = move_x.decode('latin1') + + move_up = curses.tparm(self._term_caps['move_up'].encode('latin1')) + move_up = move_up.decode('latin1') + + click.echo(move_x + move_up, nl=False, err=True) def _clear_line(self): - click.echo(self._term.clear_eol, nl=False, err=True) + assert self._term_caps is not None + + clear_eol = curses.tparm(self._term_caps['clear_eol'].encode('latin1')) + clear_eol = clear_eol.decode('latin1') + click.echo(clear_eol, nl=False, err=True) def _allocate(self): if not self._need_alloc: @@ -297,7 +297,6 @@ setup(name='BuildStream', 'ruamel.yaml < 0.15.52', 'pluginbase', 'Click', - 'blessings >= 1.6', 'jinja2 >= 2.10', 'protobuf >= 3.5', 'grpcio >= 1.10', |