summaryrefslogtreecommitdiffstats
path: root/python/mozterm
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozterm')
-rw-r--r--python/mozterm/.ruff.toml4
-rw-r--r--python/mozterm/mozterm/__init__.py4
-rw-r--r--python/mozterm/mozterm/terminal.py50
-rw-r--r--python/mozterm/mozterm/widgets.py67
-rw-r--r--python/mozterm/setup.cfg2
-rw-r--r--python/mozterm/setup.py30
-rw-r--r--python/mozterm/test/python.toml6
-rw-r--r--python/mozterm/test/test_terminal.py35
-rw-r--r--python/mozterm/test/test_widgets.py51
9 files changed, 249 insertions, 0 deletions
diff --git a/python/mozterm/.ruff.toml b/python/mozterm/.ruff.toml
new file mode 100644
index 0000000000..b3d3eaace9
--- /dev/null
+++ b/python/mozterm/.ruff.toml
@@ -0,0 +1,4 @@
+extend = "../../pyproject.toml"
+
+[isort]
+known-first-party = ["mozterm"]
diff --git a/python/mozterm/mozterm/__init__.py b/python/mozterm/mozterm/__init__.py
new file mode 100644
index 0000000000..ff15e588ff
--- /dev/null
+++ b/python/mozterm/mozterm/__init__.py
@@ -0,0 +1,4 @@
+# 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 .terminal import NullTerminal, Terminal # noqa
diff --git a/python/mozterm/mozterm/terminal.py b/python/mozterm/mozterm/terminal.py
new file mode 100644
index 0000000000..f82daa67fd
--- /dev/null
+++ b/python/mozterm/mozterm/terminal.py
@@ -0,0 +1,50 @@
+# 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/.
+
+import os
+import sys
+
+import six
+
+
+class NullTerminal(object):
+ """Replacement for `blessed.Terminal()` that does no formatting."""
+
+ number_of_colors = 0
+ width = 0
+ height = 0
+
+ def __init__(self, stream=None, **kwargs):
+ self.stream = stream or sys.__stdout__
+ try:
+ self.is_a_tty = os.isatty(self.stream.fileno())
+ except Exception:
+ self.is_a_tty = False
+
+ class NullCallableString(six.text_type):
+ """A dummy callable Unicode stolen from blessings"""
+
+ def __new__(cls):
+ new = six.text_type.__new__(cls, "")
+ return new
+
+ def __call__(self, *args):
+ if len(args) != 1 or isinstance(args[0], int):
+ return ""
+ return args[0]
+
+ def __getattr__(self, attr):
+ return self.NullCallableString()
+
+
+def Terminal(raises=False, disable_styling=False, **kwargs):
+ if disable_styling:
+ return NullTerminal(**kwargs)
+ try:
+ import blessed
+ except Exception:
+ if raises:
+ raise
+ return NullTerminal(**kwargs)
+ return blessed.Terminal(**kwargs)
diff --git a/python/mozterm/mozterm/widgets.py b/python/mozterm/mozterm/widgets.py
new file mode 100644
index 0000000000..2cf5bf250c
--- /dev/null
+++ b/python/mozterm/mozterm/widgets.py
@@ -0,0 +1,67 @@
+# 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 .terminal import Terminal
+
+DEFAULT = "\x1b(B\x1b[m"
+
+
+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
+ attribute = getattr(self.term, func)
+ # In Blessed, these attributes aren't always callable
+ if callable(attribute):
+ encoded = attribute(part)
+ else:
+ # If it's not callable, assume it's just the raw
+ # ANSI Escape Sequence and prepend it ourselves.
+ # Append DEFAULT to stop text that comes afterwards
+ # from inheriting the formatting we prepended.
+ encoded = attribute + part + DEFAULT
+ 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))
diff --git a/python/mozterm/setup.cfg b/python/mozterm/setup.cfg
new file mode 100644
index 0000000000..3c6e79cf31
--- /dev/null
+++ b/python/mozterm/setup.cfg
@@ -0,0 +1,2 @@
+[bdist_wheel]
+universal=1
diff --git a/python/mozterm/setup.py b/python/mozterm/setup.py
new file mode 100644
index 0000000000..270e87077c
--- /dev/null
+++ b/python/mozterm/setup.py
@@ -0,0 +1,30 @@
+# 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 setuptools import setup
+
+VERSION = "1.0.0"
+DEPS = ["six >= 1.13.0"]
+
+setup(
+ name="mozterm",
+ description="Terminal abstractions built around the blessed module.",
+ license="MPL 2.0",
+ author="Andrew Halberstadt",
+ author_email="ahalberstadt@mozilla.com",
+ url="",
+ packages=["mozterm"],
+ version=VERSION,
+ classifiers=[
+ "Environment :: Console",
+ "Development Status :: 3 - Alpha",
+ "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
+ "Natural Language :: English",
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.5",
+ ],
+ install_requires=DEPS,
+)
diff --git a/python/mozterm/test/python.toml b/python/mozterm/test/python.toml
new file mode 100644
index 0000000000..fd0b2f26ba
--- /dev/null
+++ b/python/mozterm/test/python.toml
@@ -0,0 +1,6 @@
+[DEFAULT]
+subsuite = "mozterm"
+
+["test_terminal.py"]
+
+["test_widgets.py"]
diff --git a/python/mozterm/test/test_terminal.py b/python/mozterm/test/test_terminal.py
new file mode 100644
index 0000000000..a24dd01ba4
--- /dev/null
+++ b/python/mozterm/test/test_terminal.py
@@ -0,0 +1,35 @@
+# 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/.
+
+import os
+import sys
+
+import mozunit
+import pytest
+
+from mozterm import NullTerminal, Terminal
+
+
+def test_terminal():
+ blessed = pytest.importorskip("blessed")
+ term = Terminal()
+ assert isinstance(term, blessed.Terminal)
+
+ term = Terminal(disable_styling=True)
+ assert isinstance(term, NullTerminal)
+
+
+def test_null_terminal():
+ term = NullTerminal()
+ assert term.red("foo") == "foo"
+ assert term.red == ""
+ assert term.color(1) == ""
+ assert term.number_of_colors == 0
+ assert term.width == 0
+ assert term.height == 0
+ assert term.is_a_tty == os.isatty(sys.stdout.fileno())
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozterm/test/test_widgets.py b/python/mozterm/test/test_widgets.py
new file mode 100644
index 0000000000..d6eb241b94
--- /dev/null
+++ b/python/mozterm/test/test_widgets.py
@@ -0,0 +1,51 @@
+# 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/.
+
+import sys
+from io import StringIO
+
+import mozunit
+import pytest
+
+from mozterm import Terminal
+from mozterm.widgets import Footer
+
+
+@pytest.fixture
+def terminal():
+ blessed = pytest.importorskip("blessed")
+
+ kind = "xterm-256color"
+ try:
+ term = Terminal(stream=StringIO(), force_styling=True, kind=kind)
+ except blessed.curses.error:
+ pytest.skip("terminal '{}' not found".format(kind))
+
+ return term
+
+
+@pytest.mark.skipif(
+ not sys.platform.startswith("win"),
+ reason="Only do ANSI Escape Sequence comparisons on Windows.",
+)
+def test_footer(terminal):
+ footer = Footer(terminal=terminal)
+ footer.write(
+ [
+ ("bright_black", "foo"),
+ ("green", "bar"),
+ ]
+ )
+ value = terminal.stream.getvalue()
+ expected = "\x1b7\x1b[90mfoo\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()