From 831dc8ab6e6535c8cca738b814b00e40dd639c8f Mon Sep 17 00:00:00 2001 From: jquast Date: Mon, 4 Nov 2013 19:00:45 -0800 Subject: fix testing height and width for integer --- blessings/tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blessings/tests.py b/blessings/tests.py index 7dda746..168c396 100644 --- a/blessings/tests.py +++ b/blessings/tests.py @@ -68,11 +68,11 @@ def test_parametrization(): eq_(TestTerminal().cup(3, 4), unicode_parm('cup', 3, 4)) -def height_and_width(): +def test_height_and_width(): """Assert that ``height_and_width()`` returns ints.""" t = TestTerminal() # kind shouldn't matter. - assert isinstance(int, t.height) - assert isinstance(int, t.width) + assert isinstance(t.height, int) + assert isinstance(t.width, int) def test_stream_attr(): -- cgit v1.2.1 From 4cff579fbf395e7cfe3d12cb1b5f53ca535d6673 Mon Sep 17 00:00:00 2001 From: jquast Date: Mon, 4 Nov 2013 19:06:54 -0800 Subject: implement height and width fallback for non-tty once the height and width test is resolved to actually test, we notice that running `nosetests 2>&1 | less' & etc. will fail: the ioctl for TIOCGWINSZ fails for non-ttys (such as used by travis CI). So, we fall-through to the LINES, COLUMNS environment variables, with default values of (24, 80). (24, 80) has been a fairly standard screensize for IBM PC-DOS and Apple ][ (beginning with the 80-column character card), and C128. It is also the default for xterm, and many classic terminals (such as a vt220) or emulating terminals (such as telix), or bulletin board servers (such as teleguard) where a 'status line' is also present. In reality, these screens are capable of *25* lines, but the 25th line is reserved for the status line. Irregardless, non-zero must be returned for 'height' and 'width' properties, as a value of 0 may become a "divide by zero" error for mathematical operations that make use of the terminal height or width in scripts that the user may chose to pipe to 'less -r' or some such. In these situations, even though a value is returned, operations such as 'move(x, y)' would still become 'stripped' due to 'is_a_tty' becoming False, so there is no actual harm in providing a terminal size that is not legal. --- blessings/__init__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/blessings/__init__.py b/blessings/__init__.py index b135e01..bee547e 100644 --- a/blessings/__init__.py +++ b/blessings/__init__.py @@ -217,7 +217,10 @@ class Terminal(object): return self._height_and_width()[1] def _height_and_width(self): - """Return a tuple of (terminal height, terminal width).""" + """Return a tuple of (terminal height, terminal width), + using TIOCGWINSZ (Terminal I/O-Control: Get Window Size), + falling back to environment variables (LINES, COLUMNS), + and 25 x 80 when otherwise unavailable.""" # tigetnum('lines') and tigetnum('cols') update only if we call # setupterm() again. for descriptor in self._init_descriptor, sys.__stdout__: @@ -225,8 +228,13 @@ class Terminal(object): return struct.unpack( 'hhhh', ioctl(descriptor, TIOCGWINSZ, '\000' * 8))[0:2] except IOError: + # when the output stream or init descriptor is not a tty, such + # as when when stdout is piped to another program, fe. tee(1), + # these ioctls will raise IOError pass - return None, None # Should never get here + lines, cols = (int(environ.get('LINES', '24')), + int(environ.get('COLUMNS', '80'))) + return lines, cols @contextmanager def location(self, x=None, y=None): -- cgit v1.2.1 From b24c3737b3733ca5cc07a7ca201c9703407c210e Mon Sep 17 00:00:00 2001 From: jquast Date: Mon, 4 Nov 2013 21:22:44 -0800 Subject: turtles all the way down for NullCallableString() allow term.color(5)('shmoo') to succeed for terminals where stream is not a tty. --- blessings/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/blessings/__init__.py b/blessings/__init__.py index b135e01..7df617d 100644 --- a/blessings/__init__.py +++ b/blessings/__init__.py @@ -518,7 +518,12 @@ class NullCallableString(unicode): # determine which of 2 special-purpose classes, # NullParametrizableString or NullFormattingString, to return, and # retire this one. - return u'' + # As a NullCallableString, even when provided with a parameter, + # such as t.color(5), we must also still be callable, fe: + # >>> t.color(5)('shmoo') + # is actually simplified result of NullCallable()(), so + # turtles all the way down: we return another instance. + return NullCallableString() return args[0] # Should we force even strs in Python 2.x to be # unicodes? No. How would I know what encoding to use # to convert it? -- cgit v1.2.1 From c7abd20192b9381b464a649898f7bae3b8eb0122 Mon Sep 17 00:00:00 2001 From: jquast Date: Mon, 4 Nov 2013 21:27:41 -0800 Subject: resolve any 'must call (at least) setupterm() first' errors avoid calling tparm when self.does_styling is False, which resolves issues with attempting to use things (such as nosetests progressive) where the terminal is not a tty. its also a "pokemon exception" and is emitted for a good reason, we certainly should not be calling tparm without calling setupterm() first ! --- blessings/__init__.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/blessings/__init__.py b/blessings/__init__.py index 7df617d..9d37889 100644 --- a/blessings/__init__.py +++ b/blessings/__init__.py @@ -294,7 +294,8 @@ class Terminal(object): :arg num: The number, 0-15, of the color """ - return ParametrizingString(self._foreground_color, self.normal) + return (ParametrizingString(self._foreground_color, self.normal) + if self.does_styling else NullCallableString()) @property def on_color(self): @@ -303,7 +304,8 @@ class Terminal(object): See ``color()``. """ - return ParametrizingString(self._background_color, self.normal) + return (ParametrizingString(self._background_color, self.normal) + if self.does_styling else NullCallableString()) @property def number_of_colors(self): @@ -325,11 +327,11 @@ class Terminal(object): # don't name it after the underlying capability, because we deviate # slightly from its behavior, and we might someday wish to give direct # access to it. - colors = tigetnum('colors') # Returns -1 if no color support, -2 if no - # such cap. + # Returns -1 if no color support, -2 if no such capability. + colors = self.does_styling and tigetnum('colors') or -1 # self.__dict__['colors'] = ret # Cache it. It's not changing. # (Doesn't work.) - return colors if colors >= 0 else 0 + return max(0, colors) def _resolve_formatter(self, attr): """Resolve a sugary or plain capability name, color, or compound @@ -439,12 +441,6 @@ class ParametrizingString(unicode): parametrized = tparm(self.encode('utf-8'), *args).decode('utf-8') return (parametrized if self._normal is None else FormattingString(parametrized, self._normal)) - except curses.error: - # Catch "must call (at least) setupterm() first" errors, as when - # running simply `nosetests` (without progressive) on nose- - # progressive. Perhaps the terminal has gone away between calling - # tigetstr and calling tparm. - return u'' except TypeError: # If the first non-int (i.e. incorrect) arg was a string, suggest # something intelligent: -- cgit v1.2.1 From 6df1e39037b36320eab5a90b8d6bdd88091e3598 Mon Sep 17 00:00:00 2001 From: jquast Date: Mon, 4 Nov 2013 23:19:15 -0800 Subject: only perform color lookup for colored terminals --- blessings/__init__.py | 2 ++ blessings/tests.py | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/blessings/__init__.py b/blessings/__init__.py index 9d37889..ca6a035 100644 --- a/blessings/__init__.py +++ b/blessings/__init__.py @@ -385,6 +385,8 @@ class Terminal(object): # bright colors at 8-15: offset = 8 if 'bright_' in color else 0 base_color = color.rsplit('_', 1)[-1] + if self.number_of_colors == 0: + return NullCallableString() return self._formatting_string( color_cap(getattr(curses, 'COLOR_' + base_color.upper()) + offset)) diff --git a/blessings/tests.py b/blessings/tests.py index 7dda746..a61b73e 100644 --- a/blessings/tests.py +++ b/blessings/tests.py @@ -137,23 +137,23 @@ def test_null_fileno(): def test_mnemonic_colors(): """Make sure color shortcuts work.""" - def color(num): - return unicode_parm('setaf', num) + def color(t, num): + return t.number_of_colors and unicode_parm('setaf', num) or '' - def on_color(num): - return unicode_parm('setab', num) + def on_color(t, num): + return t.number_of_colors and unicode_parm('setab', num) or '' # Avoid testing red, blue, yellow, and cyan, since they might someday # change depending on terminal type. t = TestTerminal() - eq_(t.white, color(7)) - eq_(t.green, color(2)) # Make sure it's different than white. - eq_(t.on_black, on_color(0)) - eq_(t.on_green, on_color(2)) - eq_(t.bright_black, color(8)) - eq_(t.bright_green, color(10)) - eq_(t.on_bright_black, on_color(8)) - eq_(t.on_bright_green, on_color(10)) + eq_(t.white, color(t, 7)) + eq_(t.green, color(t, 2)) # Make sure it's different than white. + eq_(t.on_black, on_color(t, 0)) + eq_(t.on_green, on_color(t, 2)) + eq_(t.bright_black, color(t, 8)) + eq_(t.bright_green, color(t, 10)) + eq_(t.on_bright_black, on_color(t, 8)) + eq_(t.on_bright_green, on_color(t, 10)) def test_callable_numeric_colors(): -- cgit v1.2.1 From 132db93c939b257cbd7a4642e0fb7d3256d968e1 Mon Sep 17 00:00:00 2001 From: jquast Date: Mon, 4 Nov 2013 23:23:12 -0800 Subject: Revert "Merge remote-tracking branch 'origin/bugfix-3' into bugfix-1" This reverts commit 6d8fe019be1e1c1bd1238980288e1451d99eba80, reversing changes made to 4cff579fbf395e7cfe3d12cb1b5f53ca535d6673. --- blessings/__init__.py | 27 ++++++++++++--------------- blessings/tests.py | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/blessings/__init__.py b/blessings/__init__.py index f7c1659..bee547e 100644 --- a/blessings/__init__.py +++ b/blessings/__init__.py @@ -302,8 +302,7 @@ class Terminal(object): :arg num: The number, 0-15, of the color """ - return (ParametrizingString(self._foreground_color, self.normal) - if self.does_styling else NullCallableString()) + return ParametrizingString(self._foreground_color, self.normal) @property def on_color(self): @@ -312,8 +311,7 @@ class Terminal(object): See ``color()``. """ - return (ParametrizingString(self._background_color, self.normal) - if self.does_styling else NullCallableString()) + return ParametrizingString(self._background_color, self.normal) @property def number_of_colors(self): @@ -335,11 +333,11 @@ class Terminal(object): # don't name it after the underlying capability, because we deviate # slightly from its behavior, and we might someday wish to give direct # access to it. - # Returns -1 if no color support, -2 if no such capability. - colors = self.does_styling and tigetnum('colors') or -1 + colors = tigetnum('colors') # Returns -1 if no color support, -2 if no + # such cap. # self.__dict__['colors'] = ret # Cache it. It's not changing. # (Doesn't work.) - return max(0, colors) + return colors if colors >= 0 else 0 def _resolve_formatter(self, attr): """Resolve a sugary or plain capability name, color, or compound @@ -393,8 +391,6 @@ class Terminal(object): # bright colors at 8-15: offset = 8 if 'bright_' in color else 0 base_color = color.rsplit('_', 1)[-1] - if self.number_of_colors == 0: - return NullCallableString() return self._formatting_string( color_cap(getattr(curses, 'COLOR_' + base_color.upper()) + offset)) @@ -451,6 +447,12 @@ class ParametrizingString(unicode): parametrized = tparm(self.encode('utf-8'), *args).decode('utf-8') return (parametrized if self._normal is None else FormattingString(parametrized, self._normal)) + except curses.error: + # Catch "must call (at least) setupterm() first" errors, as when + # running simply `nosetests` (without progressive) on nose- + # progressive. Perhaps the terminal has gone away between calling + # tigetstr and calling tparm. + return u'' except TypeError: # If the first non-int (i.e. incorrect) arg was a string, suggest # something intelligent: @@ -524,12 +526,7 @@ class NullCallableString(unicode): # determine which of 2 special-purpose classes, # NullParametrizableString or NullFormattingString, to return, and # retire this one. - # As a NullCallableString, even when provided with a parameter, - # such as t.color(5), we must also still be callable, fe: - # >>> t.color(5)('shmoo') - # is actually simplified result of NullCallable()(), so - # turtles all the way down: we return another instance. - return NullCallableString() + return u'' return args[0] # Should we force even strs in Python 2.x to be # unicodes? No. How would I know what encoding to use # to convert it? diff --git a/blessings/tests.py b/blessings/tests.py index ee9c3c8..168c396 100644 --- a/blessings/tests.py +++ b/blessings/tests.py @@ -137,23 +137,23 @@ def test_null_fileno(): def test_mnemonic_colors(): """Make sure color shortcuts work.""" - def color(t, num): - return t.number_of_colors and unicode_parm('setaf', num) or '' + def color(num): + return unicode_parm('setaf', num) - def on_color(t, num): - return t.number_of_colors and unicode_parm('setab', num) or '' + def on_color(num): + return unicode_parm('setab', num) # Avoid testing red, blue, yellow, and cyan, since they might someday # change depending on terminal type. t = TestTerminal() - eq_(t.white, color(t, 7)) - eq_(t.green, color(t, 2)) # Make sure it's different than white. - eq_(t.on_black, on_color(t, 0)) - eq_(t.on_green, on_color(t, 2)) - eq_(t.bright_black, color(t, 8)) - eq_(t.bright_green, color(t, 10)) - eq_(t.on_bright_black, on_color(t, 8)) - eq_(t.on_bright_green, on_color(t, 10)) + eq_(t.white, color(7)) + eq_(t.green, color(2)) # Make sure it's different than white. + eq_(t.on_black, on_color(0)) + eq_(t.on_green, on_color(2)) + eq_(t.bright_black, color(8)) + eq_(t.bright_green, color(10)) + eq_(t.on_bright_black, on_color(8)) + eq_(t.on_bright_green, on_color(10)) def test_callable_numeric_colors(): -- cgit v1.2.1 From f86d9dbb794bad383b7cfb79989954d3aa0bdfc2 Mon Sep 17 00:00:00 2001 From: jquast Date: Tue, 5 Nov 2013 00:09:58 -0800 Subject: docfix: 25 x 80 -> 24 x 80 --- blessings/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blessings/__init__.py b/blessings/__init__.py index bee547e..8183265 100644 --- a/blessings/__init__.py +++ b/blessings/__init__.py @@ -220,7 +220,7 @@ class Terminal(object): """Return a tuple of (terminal height, terminal width), using TIOCGWINSZ (Terminal I/O-Control: Get Window Size), falling back to environment variables (LINES, COLUMNS), - and 25 x 80 when otherwise unavailable.""" + and 24 x 80 when otherwise unavailable.""" # tigetnum('lines') and tigetnum('cols') update only if we call # setupterm() again. for descriptor in self._init_descriptor, sys.__stdout__: -- cgit v1.2.1 From c3ca6a4e14867370593491abf5dc8ffe44fd6041 Mon Sep 17 00:00:00 2001 From: Erik Rose Date: Tue, 5 Nov 2013 20:44:35 -0500 Subject: Remove fallback to 24x80, and fix docstring formatting. There are some advantages to falling back to 24x80: for example, the ability to easily render output based on height and width when being piped through `less`. However, that's a backward incompatible change--we explicitly documented the None return values in earlier versions--so I don't want to make it lightly. Reverting it for now because I want to get the unquestionable parts of this merged in. --- blessings/__init__.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/blessings/__init__.py b/blessings/__init__.py index 8183265..4506869 100644 --- a/blessings/__init__.py +++ b/blessings/__init__.py @@ -217,10 +217,13 @@ class Terminal(object): return self._height_and_width()[1] def _height_and_width(self): - """Return a tuple of (terminal height, terminal width), - using TIOCGWINSZ (Terminal I/O-Control: Get Window Size), - falling back to environment variables (LINES, COLUMNS), - and 24 x 80 when otherwise unavailable.""" + """Return a tuple of (terminal height, terminal width). + + Start by trying TIOCGWINSZ (Terminal I/O-Control: Get Window Size), + falling back to environment variables (LINES, COLUMNS), and returning + (None, None) if those are unavailable or invalid. + + """ # tigetnum('lines') and tigetnum('cols') update only if we call # setupterm() again. for descriptor in self._init_descriptor, sys.__stdout__: @@ -232,9 +235,10 @@ class Terminal(object): # as when when stdout is piped to another program, fe. tee(1), # these ioctls will raise IOError pass - lines, cols = (int(environ.get('LINES', '24')), - int(environ.get('COLUMNS', '80'))) - return lines, cols + try: + return int(environ.get('LINES')), int(environ.get('COLUMNS')) + except TypeError: + return None, None @contextmanager def location(self, x=None, y=None): -- cgit v1.2.1 From 776f2725c0fa1e357f96776f1bc1ee0ea4543f11 Mon Sep 17 00:00:00 2001 From: Erik Rose Date: Tue, 5 Nov 2013 20:48:25 -0500 Subject: Update version history. --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index cfa56c5..7336ebd 100644 --- a/README.rst +++ b/README.rst @@ -439,6 +439,8 @@ Version History * Make ``is_a_tty`` a read-only property, like ``does_styling``. Writing to it never would have done anything constructive. * Add ``fullscreen()`` and ``hidden_cursor()`` to the auto-generated docs. + * Fall back to ``LINES`` and ``COLUMNS`` environment vars to find height and + width. (jquast) 1.5.1 * Clean up fabfile, removing the redundant ``test`` command. -- cgit v1.2.1