summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTristan Van Berkom <tristan.vanberkom@codethink.co.uk>2018-09-19 18:49:53 +0900
committerTristan Van Berkom <tristan.vanberkom@codethink.co.uk>2018-09-19 18:56:17 +0900
commit7ba047b47f34715b57426259be48866283860a14 (patch)
tree0963f993742f9a9f433a9f913baf0526be0a043d
parent9c1847b95aa950abc639b216d4f2686b42813601 (diff)
downloadbuildstream-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.py2
-rw-r--r--buildstream/_frontend/status.py82
-rwxr-xr-xsetup.py1
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:
diff --git a/setup.py b/setup.py
index 781b55bcc..a0686ebea 100755
--- a/setup.py
+++ b/setup.py
@@ -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',