Bug 1422302 - Create python/mozterm for sharing terminal blessings across modules
This is a new module that will provide a place to store some common
abstractions around the 'blessings' module. The main entrypoint is:
from mozterm import Terminal
term = Terminal()
If blessings is available, this will return a blessings.Terminal()
object. If it isn't available, or something went wrong on import,
this will return a NullTerminal() object, which is a drop-in
replacement that does no formatting.
MozReview-Commit-ID: 6c63svm4tM5
--- a/build/virtualenv_packages.txt
+++ b/build/virtualenv_packages.txt
@@ -1,12 +1,13 @@
mozilla.pth:python/mach
mozilla.pth:python/mozboot
mozilla.pth:python/mozbuild
mozilla.pth:python/mozlint
+mozilla.pth:python/mozterm
mozilla.pth:python/mozversioncontrol
mozilla.pth:third_party/python/blessings
mozilla.pth:third_party/python/compare-locales
mozilla.pth:third_party/python/configobj
mozilla.pth:third_party/python/cram
mozilla.pth:third_party/python/dlmanager
mozilla.pth:third_party/python/fluent
mozilla.pth:third_party/python/futures
--- a/python/moz.build
+++ b/python/moz.build
@@ -35,16 +35,17 @@ SPHINX_PYTHON_PACKAGE_DIRS += [
]
SPHINX_TREES['mach'] = 'mach/docs'
PYTHON_UNITTEST_MANIFESTS += [
'mach/mach/test/python.ini',
'mozbuild/dumbmake/test/python.ini',
'mozlint/test/python.ini',
+ 'mozterm/test/python.ini',
'mozversioncontrol/test/python.ini',
]
if CONFIG['MOZ_BUILD_APP']:
PYTHON_UNITTEST_MANIFESTS += [
'mozbuild/mozbuild/test/python.ini',
'mozbuild/mozpack/test/python.ini',
]
--- a/python/mozlint/mozlint/formatters/stylish.py
+++ b/python/mozlint/mozlint/formatters/stylish.py
@@ -1,37 +1,17 @@
# 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 ..result import ResultContainer
-
-try:
- import blessings
-except ImportError:
- blessings = None
-
+from mozterm import Terminal
-class NullTerminal(object):
- """Replacement for `blessings.Terminal()` that does no formatting."""
- class NullCallableString(unicode):
- """A dummy callable Unicode stolen from blessings"""
- def __new__(cls):
- new = unicode.__new__(cls, u'')
- return new
-
- def __call__(self, *args):
- if len(args) != 1 or isinstance(args[0], int):
- return u''
- return args[0]
-
- def __getattr__(self, attr):
- return self.NullCallableString()
+from ..result import ResultContainer
class StylishFormatter(object):
"""Formatter based on the eslint default."""
# Colors later on in the list are fallbacks in case the terminal
# doesn't support colors earlier in the list.
# See http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html
@@ -40,21 +20,18 @@ class StylishFormatter(object):
'red': [1],
'yellow': [3],
'brightred': [9, 1],
'brightyellow': [11, 3],
}
fmt = " {c1}{lineno}{column} {c2}{level}{normal} {message} {c1}{rule}({linter}){normal}"
fmt_summary = "{t.bold}{c}\u2716 {problem} ({error}, {warning}{failure}){t.normal}"
- def __init__(self, disable_colors=None):
- if disable_colors or not blessings:
- self.term = NullTerminal()
- else:
- self.term = blessings.Terminal()
+ def __init__(self, disable_colors=False):
+ self.term = Terminal(disable_styling=disable_colors)
self.num_colors = self.term.number_of_colors
def color(self, color):
for num in self._colors[color]:
if num < self.num_colors:
return self.term.color(num)
return ''
new file mode 100644
--- /dev/null
+++ b/python/mozterm/mozterm/__init__.py
@@ -0,0 +1,6 @@
+# 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, NullTerminal # noqa
new file mode 100644
--- /dev/null
+++ b/python/mozterm/mozterm/terminal.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
+
+import os
+import sys
+
+
+class NullTerminal(object):
+ """Replacement for `blessings.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:
+ self.is_a_tty = False
+
+ class NullCallableString(unicode):
+ """A dummy callable Unicode stolen from blessings"""
+ def __new__(cls):
+ new = unicode.__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 blessings
+ except Exception:
+ if raises:
+ raise
+ return NullTerminal(**kwargs)
+ return blessings.Terminal(**kwargs)
new file mode 100644
--- /dev/null
+++ b/python/mozterm/test/python.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+subsuite = mozterm
+
+[test_terminal.py]
new file mode 100644
--- /dev/null
+++ b/python/mozterm/test/test_terminal.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/.
+
+from __future__ import absolute_import, unicode_literals
+
+import os
+import sys
+
+import mozunit
+import pytest
+
+from mozterm import Terminal, NullTerminal
+
+
+def test_terminal():
+ blessings = pytest.importorskip('blessings')
+ term = Terminal()
+ assert isinstance(term, blessings.Terminal)
+
+ term = Terminal(disable_styling=True)
+ assert isinstance(term, NullTerminal)
+
+ del sys.modules['blessings']
+ orig = sys.path[:]
+ for path in orig:
+ if 'blessings' in path:
+ sys.path.remove(path)
+
+ term = Terminal()
+ assert isinstance(term, NullTerminal)
+
+ with pytest.raises(ImportError):
+ term = Terminal(raises=True)
+
+ sys.path = orig
+
+
+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()
--- a/taskcluster/ci/source-test/python.yml
+++ b/taskcluster/ci/source-test/python.yml
@@ -123,16 +123,32 @@ mozlint:
docker-image: {in-tree: "lint"}
max-run-time: 3600
run:
mach: python-test --subsuite mozlint
when:
files-changed:
- 'python/mozlint/**'
+mozterm:
+ description: python/mozterm unit tests
+ platform: linux64/opt
+ treeherder:
+ symbol: py(term)
+ worker:
+ by-platform:
+ linux64.*:
+ docker-image: {in-tree: "lint"}
+ max-run-time: 3600
+ run:
+ mach: python-test --subsuite mozterm
+ when:
+ files-changed:
+ - 'python/mozterm/**'
+
mozversioncontrol:
description: python/mozversioncontrol unit tests
platform: linux64/opt
treeherder:
symbol: py(vcs)
worker:
by-platform:
linux64.*:
--- a/tools/lint/flake8.yml
+++ b/tools/lint/flake8.yml
@@ -7,16 +7,17 @@ flake8:
- configure.py
- config/check_macroassembler_style.py
- config/mozunit.py
- layout/tools/reftest
- python/mach
- python/mach_commands.py
- python/mozboot
- python/mozlint
+ - python/mozterm
- python/mozversioncontrol
- security/manager
- taskcluster
- testing/firefox-ui
- testing/mach_commands.py
- testing/marionette/client
- testing/marionette/harness
- testing/marionette/puppeteer
--- a/tools/tryselect/selectors/fuzzy.py
+++ b/tools/tryselect/selectors/fuzzy.py
@@ -6,28 +6,25 @@ from __future__ import absolute_import,
import os
import platform
import subprocess
import sys
from distutils.spawn import find_executable
from mozboot.util import get_state_dir
+from mozterm import Terminal
from .. import preset as pset
from ..cli import BaseTryParser
from ..tasks import generate_tasks
from ..vcs import VCSHelper
-try:
- import blessings
- terminal = blessings.Terminal()
-except ImportError:
- from mozlint.formatters.stylish import NullTerminal
- terminal = NullTerminal()
+terminal = Terminal()
+
FZF_NOT_FOUND = """
Could not find the `fzf` binary.
The `mach try fuzzy` command depends on fzf. Please install it following the
appropriate instructions for your platform:
https://github.com/junegunn/fzf#installation