Bug 1422302 - Move mozbuild.controller.building.Footer to mozterm r=gps
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 01 Dec 2017 09:59:54 -0500
changeset 395100 7d01faca3c8eff3300cb08f75d53cf5f99bb5c28
parent 395099 ea36374ea21af2a2b677645116e4a86e3745fe99
child 395101 312e536acf9c3366216097630dccb690acb3df17
push id56597
push userahalberstadt@mozilla.com
push dateTue, 05 Dec 2017 19:19:11 +0000
treeherderautoland@7d01faca3c8e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1422302
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1422302 - Move mozbuild.controller.building.Footer to mozterm r=gps This makes it a bit easier to share with other parts of the tree, like test and linting. MozReview-Commit-ID: 8Gzk8uOF5zK
python/mozbuild/mozbuild/controller/building.py
python/mozterm/mozterm/widgets.py
python/mozterm/test/python.ini
python/mozterm/test/test_widgets.py
--- a/python/mozbuild/mozbuild/controller/building.py
+++ b/python/mozbuild/mozbuild/controller/building.py
@@ -26,16 +26,17 @@ from textwrap import (
 
 try:
     import psutil
 except Exception:
     psutil = None
 
 from mach.mixin.logging import LoggingMixin
 from mozsystemmonitor.resourcemonitor import SystemResourceMonitor
+from mozterm.widgets import Footer
 
 import mozpack.path as mozpath
 
 from .clobber import (
     Clobberer,
 )
 from ..base import (
     BuildEnvironmentNotFoundException,
@@ -558,70 +559,16 @@ class TerminalLoggingHandler(logging.Han
                 self.footer.draw()
 
             # If we don't flush, the footer may not get drawn.
             self.fh.flush()
         finally:
             self.release()
 
 
-class Footer(object):
-    """Handles display of a footer in a terminal.
-
-    This class implements the functionality common to all mach commands
-    that render a footer.
-    """
-
-    def __init__(self, terminal):
-        # terminal is a blessings.Terminal.
-        self._t = terminal
-        self._fh = sys.stdout
-
-    def clear(self):
-        """Removes the footer from the current terminal."""
-        self._fh.write(self._t.move_x(0))
-        self._fh.write(self._t.clear_eol())
-
-    def write(self, parts):
-        """Write some output in the footer, accounting for terminal width.
-
-        parts is a list of 2-tuples of (encoding_function, input).
-        None means no encoding."""
-
-        # We don't want to write more characters than the current width of the
-        # terminal otherwise wrapping may result in weird behavior. We can't
-        # simply truncate the line at terminal width characters because a)
-        # non-viewable escape characters count towards the limit and b) we
-        # don't want to truncate in the middle of an escape sequence because
-        # subsequent output would inherit the escape sequence.
-        max_width = self._t.width
-        written = 0
-        write_pieces = []
-        for part in parts:
-            try:
-                func, part = part
-                encoded = getattr(self._t, func)(part)
-            except ValueError:
-                encoded = part
-
-            len_part = len(part)
-            len_spaces = len(write_pieces)
-            if written + len_part + len_spaces > max_width:
-                write_pieces.append(part[0:max_width - written - len_spaces])
-                written += len_part
-                break
-
-            write_pieces.append(encoded)
-            written += len_part
-
-        with self._t.location():
-            self._t.move(self._t.height-1,0)
-            self._fh.write(' '.join(write_pieces))
-
-
 class BuildProgressFooter(Footer):
     """Handles display of a build progress indicator in a terminal.
 
     When mach builds inside a blessings-supported terminal, it will render
     progress information collected from a BuildMonitor. This class converts the
     state of BuildMonitor into terminal output.
     """
 
@@ -646,17 +593,16 @@ class BuildProgressFooter(Footer):
             elif status == 'finished':
                 append(('green', tier))
             else:
                 append(('underline_yellow', tier))
 
         self.write(parts)
 
 
-
 class OutputManager(LoggingMixin):
     """Handles writing job output to a terminal or log."""
 
     def __init__(self, log_manager, footer):
         self.populate_logger()
 
         self.footer = None
         terminal = log_manager.terminal
new file mode 100644
--- /dev/null
+++ b/python/mozterm/mozterm/widgets.py
@@ -0,0 +1,58 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, unicode_literals
+
+from .terminal import Terminal
+
+
+class BaseWidget(object):
+    def __init__(self, terminal=None):
+        self.term = terminal or Terminal()
+        self.stream = self.term.stream
+
+
+class Footer(BaseWidget):
+    """Handles display of a footer in a terminal."""
+
+    def clear(self):
+        """Removes the footer from the current terminal."""
+        self.stream.write(self.term.move_x(0))
+        self.stream.write(self.term.clear_eol())
+
+    def write(self, parts):
+        """Write some output in the footer, accounting for terminal width.
+
+        parts is a list of 2-tuples of (encoding_function, input).
+        None means no encoding."""
+
+        # We don't want to write more characters than the current width of the
+        # terminal otherwise wrapping may result in weird behavior. We can't
+        # simply truncate the line at terminal width characters because a)
+        # non-viewable escape characters count towards the limit and b) we
+        # don't want to truncate in the middle of an escape sequence because
+        # subsequent output would inherit the escape sequence.
+        max_width = self.term.width
+        written = 0
+        write_pieces = []
+        for part in parts:
+            try:
+                func, part = part
+                encoded = getattr(self.term, func)(part)
+            except ValueError:
+                encoded = part
+
+            len_part = len(part)
+            len_spaces = len(write_pieces)
+            if written + len_part + len_spaces > max_width:
+                write_pieces.append(part[0:max_width - written - len_spaces])
+                written += len_part
+                break
+
+            write_pieces.append(encoded)
+            written += len_part
+
+        with self.term.location():
+            self.term.move(self.term.height-1, 0)
+            self.stream.write(' '.join(write_pieces))
--- a/python/mozterm/test/python.ini
+++ b/python/mozterm/test/python.ini
@@ -1,4 +1,5 @@
 [DEFAULT]
 subsuite = mozterm
 
 [test_terminal.py]
+[test_widgets.py]
new file mode 100644
--- /dev/null
+++ b/python/mozterm/test/test_widgets.py
@@ -0,0 +1,49 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, unicode_literals
+
+from io import StringIO
+
+import mozunit
+import pytest
+
+from mozterm import Terminal
+from mozterm.widgets import Footer
+
+
+@pytest.fixture
+def terminal(monkeypatch):
+    blessings = pytest.importorskip('blessings')
+
+    kind = 'xterm-256color'
+    try:
+        term = Terminal(stream=StringIO(), force_styling=True, kind=kind)
+    except blessings.curses.error:
+        pytest.skip("terminal '{}' not found".format(kind))
+
+    # For some reason blessings returns None for width/height though a comment
+    # says that shouldn't ever happen.
+    monkeypatch.setattr(term, '_height_and_width', lambda: (100, 100))
+    return term
+
+
+def test_footer(terminal):
+    footer = Footer(terminal=terminal)
+    footer.write([
+        ('dim', 'foo'),
+        ('green', 'bar'),
+    ])
+    value = terminal.stream.getvalue()
+    expected = "\x1b7\x1b[2mfoo\x1b(B\x1b[m \x1b[32mbar\x1b(B\x1b[m\x1b8"
+    assert value == expected
+
+    footer.clear()
+    value = terminal.stream.getvalue()[len(value):]
+    expected = "\x1b[1G\x1b[K"
+    assert value == expected
+
+
+if __name__ == '__main__':
+    mozunit.main()