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.van.berkom@gmail.com>2018-09-19 11:08:33 +0000
commit16d9c6e55e23466ebb37232f492a87c4baad3cc1 (patch)
tree62cc1a11783333058f06902562e9a0715913a280
parentdddd6025705d4553f857695bb2b5e6bde6943556 (diff)
downloadbuildstream-16d9c6e55e23466ebb37232f492a87c4baad3cc1.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.py3
-rw-r--r--buildstream/_frontend/status.py82
-rwxr-xr-xsetup.py1
3 files changed, 77 insertions, 9 deletions
diff --git a/buildstream/_frontend/app.py b/buildstream/_frontend/app.py
index 1e357f123..ccdbb2d5d 100644
--- a/buildstream/_frontend/app.py
+++ b/buildstream/_frontend/app.py
@@ -26,7 +26,6 @@ import datetime
from textwrap import TextWrapper
import click
from click import UsageError
-from blessings import Terminal
# Import buildstream public symbols
from .. import Scope
@@ -92,7 +91,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',