diff options
author | Erik Rose <erik@mozilla.com> | 2011-12-09 19:55:20 -0500 |
---|---|---|
committer | Erik Rose <erik@mozilla.com> | 2011-12-09 19:55:20 -0500 |
commit | 17188dce3b694b955fd5815369343154cb7bf8dd (patch) | |
tree | 22c010c29042ac60c0746c7878fb16ff5ae08f08 | |
parent | 8151e6fe390046a41a2153f4004baec8f20f35d4 (diff) | |
download | blessings-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.rst | 3 | ||||
-rw-r--r-- | blessings/__init__.py | 55 | ||||
-rw-r--r-- | blessings/tests.py | 6 |
3 files changed, 46 insertions, 18 deletions
@@ -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) |