summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTristan Van Berkom <tristan.van.berkom@gmail.com>2018-09-19 11:27:55 +0000
committerTristan Van Berkom <tristan.van.berkom@gmail.com>2018-09-19 11:27:55 +0000
commit7dd74b01de22d29bcd2abd76791822fd12236ec6 (patch)
tree62cc1a11783333058f06902562e9a0715913a280
parentdddd6025705d4553f857695bb2b5e6bde6943556 (diff)
parent16d9c6e55e23466ebb37232f492a87c4baad3cc1 (diff)
downloadbuildstream-7dd74b01de22d29bcd2abd76791822fd12236ec6.tar.gz
Merge branch 'tristan/remove-blessings' into 'master'
_frontend/status.py: Completely remove the blessings dependency from BuildStream See merge request BuildStream/buildstream!808
-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',