summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErik Rose <erik@mozilla.com>2011-12-09 19:55:20 -0500
committerErik Rose <erik@mozilla.com>2011-12-09 19:55:20 -0500
commit17188dce3b694b955fd5815369343154cb7bf8dd (patch)
tree22c010c29042ac60c0746c7878fb16ff5ae08f08
parent8151e6fe390046a41a2153f4004baec8f20f35d4 (diff)
downloadblessings-17188dce3b694b955fd5815369343154cb7bf8dd.tar.gz
Make `height` and `width` examine any passed-in stream before falling back to stdout. Make sure we can always get a height and width, even on non-tty Terminals.
-rw-r--r--README.rst3
-rw-r--r--blessings/__init__.py55
-rw-r--r--blessings/tests.py6
3 files changed, 46 insertions, 18 deletions
diff --git a/README.rst b/README.rst
index 3214d2e..af88dfb 100644
--- a/README.rst
+++ b/README.rst
@@ -344,6 +344,9 @@ Version History
``setb`` capabilities if the ANSI ``setaf`` and ``setab`` aren't available.
* Allow ``color`` attr to act as an unparametrized string, not just a
callable.
+ * Make ``height`` and ``width`` examine any passed-in stream before falling
+ back to stdout. (This rarely if ever affects its actual behavior; it's mostly
+ philosophical.)
* Make caching more efficient.
* Get rid of a reference cycle between Terminals and FormattingStrings.
* Update docs to reflect that terminal addressing (as in ``location()``) is
diff --git a/blessings/__init__.py b/blessings/__init__.py
index 7ebd8ad..081288b 100644
--- a/blessings/__init__.py
+++ b/blessings/__init__.py
@@ -81,20 +81,21 @@ class Terminal(object):
self.is_a_tty = stream_descriptor is not None and isatty(stream_descriptor)
self._does_styling = self.is_a_tty or force_styling
- if self._does_styling:
- # The desciptor to direct terminal initialization sequences to.
- # sys.__stdout__ seems to always have a descriptor of 1, even if
- # output is redirected.
- init_descriptor = (sys.__stdout__.fileno() if stream_descriptor is None
- else stream_descriptor)
+ # The desciptor to direct terminal initialization sequences to.
+ # sys.__stdout__ seems to always have a descriptor of 1, even if output
+ # is redirected.
+ self._init_descriptor = (sys.__stdout__.fileno()
+ if stream_descriptor is None
+ else stream_descriptor)
+ if self._does_styling:
# Make things like tigetstr() work. Explicit args make setupterm()
# work even when -s is passed to nosetests. Lean toward sending
# init sequences to the stream if it has a file descriptor, and
# send them to stdout as a fallback, since they have to go
# somewhere.
setupterm(kind or environ.get('TERM', 'unknown'),
- init_descriptor)
+ self._init_descriptor)
self.stream = stream
@@ -159,13 +160,38 @@ class Terminal(object):
@property
def height(self):
- """The height of the terminal in characters"""
- return height_and_width()[0]
+ """The height of the terminal in characters
+
+ If no stream or a stream not representing a terminal was passed in at
+ construction, return the dimension of the controlling terminal so
+ piping to things that eventually display on the terminal (like ``less
+ -R``) work. If a stream representing a terminal was passed in, return
+ the dimensions of that terminal. If there somehow is no controlling
+ terminal, return ``None``. (Thus, you should check that ``is_a_tty`` is
+ true before doing any math on the result.)
+
+ """
+ return self._height_and_width()[0]
@property
def width(self):
- """The width of the terminal in characters"""
- return height_and_width()[1]
+ """The width of the terminal in characters
+
+ See ``height()`` for some corner cases.
+
+ """
+ return self._height_and_width()[1]
+
+ def _height_and_width(self):
+ """Return a tuple of (terminal height, terminal width)."""
+ # tigetnum('lines') and tigetnum('cols') update only if we call
+ # setupterm() again.
+ for descriptor in self._init_descriptor, sys.__stdout__:
+ try:
+ return struct.unpack('hhhh', ioctl(descriptor, TIOCGWINSZ, '\000' * 8))[0:2]
+ except IOError:
+ pass
+ return None, None # Should never get here
def location(self, x=None, y=None):
"""Return a context manager for temporarily moving the cursor.
@@ -385,13 +411,6 @@ class NullCallableString(unicode):
return arg # TODO: Force even strs in Python 2.x to be unicodes? Nah. How would I know what encoding to use to convert it?
-def height_and_width():
- """Return a tuple of (terminal height, terminal width)."""
- # tigetnum('lines') and tigetnum('cols') apparently don't update while
- # nose-progressive's progress bar is running.
- return struct.unpack('hhhh', ioctl(0, TIOCGWINSZ, '\000' * 8))[0:2]
-
-
def split_into_formatters(compound):
"""Split a possibly compound format string into segments.
diff --git a/blessings/tests.py b/blessings/tests.py
index f37bd72..a02a392 100644
--- a/blessings/tests.py
+++ b/blessings/tests.py
@@ -223,3 +223,9 @@ def test_nice_formatting_errors():
t.bold_misspelled('a', 'b') # >1 string arg
except TypeError, e:
assert 'probably misspelled' not in e.args[0]
+
+
+def test_init_descriptor_always_initted():
+ """We should be able to get a height and width even on no-tty Terminals."""
+ t = Terminal(stream=StringIO())
+ eq_(type(t.height), int)