Bug 1422302 - Move mozbuild.controller.building.Footer to mozterm draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 01 Dec 2017 09:59:54 -0500
changeset 707735 4291222f6cbf037d9fd5104287b880e4488e3278
parent 707734 0cce8b5be51ca7d705b457dc08170af0394eaecb
child 708694 80f2e9f4f52fb75930edb70cb10ee20b434ab038
push id92201
push userahalberstadt@mozilla.com
push dateTue, 05 Dec 2017 18:53:12 +0000
bugs1422302
milestone59.0a1
Bug 1422302 - Move mozbuild.controller.building.Footer to mozterm 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()