summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2012-04-15 19:35:56 +0100
committerLars Wirzenius <liw@liw.fi>2012-04-15 19:35:56 +0100
commit394d6f5401cd79eb7b1624d23f3d8f4804144c78 (patch)
tree7861532a671a3c5e629e8242bc607648826aef85
parent1623d3599bd057677158bfd2167798585d2afc44 (diff)
parent1a4675e03b42fcf276cf6a160480010cb36ca88c (diff)
downloadttystatus-394d6f5401cd79eb7b1624d23f3d8f4804144c78.tar.gz
Fix a lot of rendering bugs by fixing internal abstration
Previously, we told update() how much width there was, but that meant update() also had to render. That was slow. Then we fixed that by only updating when it was time to update the display, but that obviously doesn't work either. And when I say obviously, I didn't think of it at the time. Now, this should work, since we always update the values, so counters etc get incremented correctly, but then only do the actual rendering when it's actually time to actually write actual text to actual output. Actually.
-rw-r--r--NEWS5
-rw-r--r--ttystatus/bytesize.py12
-rw-r--r--ttystatus/bytesize_tests.py33
-rw-r--r--ttystatus/bytespeed.py7
-rw-r--r--ttystatus/bytespeed_tests.py27
-rw-r--r--ttystatus/counter.py7
-rw-r--r--ttystatus/counter_tests.py21
-rw-r--r--ttystatus/elapsed.py5
-rw-r--r--ttystatus/elapsed_tests.py15
-rw-r--r--ttystatus/fmt_tests.py8
-rw-r--r--ttystatus/index.py7
-rw-r--r--ttystatus/index_tests.py13
-rw-r--r--ttystatus/integer.py19
-rw-r--r--ttystatus/integer_tests.py13
-rw-r--r--ttystatus/literal.py3
-rw-r--r--ttystatus/literal_tests.py9
-rw-r--r--ttystatus/pathname.py20
-rw-r--r--ttystatus/pathname_tests.py31
-rw-r--r--ttystatus/percent.py7
-rw-r--r--ttystatus/percent_tests.py17
-rw-r--r--ttystatus/progressbar.py14
-rw-r--r--ttystatus/progressbar_tests.py38
-rw-r--r--ttystatus/remtime.py5
-rw-r--r--ttystatus/remtime_tests.py33
-rw-r--r--ttystatus/status.py45
-rw-r--r--ttystatus/status_tests.py56
-rw-r--r--ttystatus/string.py7
-rw-r--r--ttystatus/string_tests.py13
-rw-r--r--ttystatus/widget.py63
29 files changed, 303 insertions, 250 deletions
diff --git a/NEWS b/NEWS
index 31f8482..63dcdb9 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,11 @@
NEWS file for ttystatus
=======================
+Version 0.17, released UNRELEASED
+---------------------------------
+
+* Committed genocide towards whole civilizations of rendering problems.
+
Version 0.16, released 2012-04-08
---------------------------------
diff --git a/ttystatus/bytesize.py b/ttystatus/bytesize.py
index f47d453..13ee68b 100644
--- a/ttystatus/bytesize.py
+++ b/ttystatus/bytesize.py
@@ -20,13 +20,17 @@ import ttystatus
class ByteSize(ttystatus.Widget):
'''Display data size in bytes, KiB, etc.'''
+
+ static_width = False
def __init__(self, name):
self.name = name
- self.interesting_keys = [name]
self._bytes = 0
+
+ def update(self, ts):
+ self._bytes = ts[self.name]
- def format(self):
+ def render(self, width):
units = (
(1024**4, 2, 'TiB'),
(1024**3, 2, 'GiB'),
@@ -40,6 +44,4 @@ class ByteSize(ttystatus.Widget):
float(self._bytes) / float(factor),
unit)
return '%d B' % self._bytes
-
- def update(self, master, width):
- self._bytes = master[self.name]
+
diff --git a/ttystatus/bytesize_tests.py b/ttystatus/bytesize_tests.py
index 11958f4..15a0ff5 100644
--- a/ttystatus/bytesize_tests.py
+++ b/ttystatus/bytesize_tests.py
@@ -24,34 +24,37 @@ class ByteSizeTests(unittest.TestCase):
def setUp(self):
self.w = ttystatus.ByteSize('foo')
+ def test_is_not_static_width(self):
+ self.assertFalse(self.w.static_width)
+
def test_formats_zero_bytes_without_update(self):
- self.assertEqual(str(self.w), '0 B')
+ self.assertEqual(self.w.render(0), '0 B')
def test_formats_zero_bytes_correctly(self):
- self.w.update({ 'foo': 0 }, 999)
- self.assertEqual(str(self.w), '0 B')
+ self.w.update({ 'foo': 0 })
+ self.assertEqual(self.w.render(0), '0 B')
def test_formats_one_bytes_correctly(self):
- self.w.update({ 'foo': 1 }, 999)
- self.assertEqual(str(self.w), '1 B')
+ self.w.update({ 'foo': 1 })
+ self.assertEqual(self.w.render(0), '1 B')
def test_formats_1023_bytes_correctly(self):
- self.w.update({ 'foo': 1023 }, 999)
- self.assertEqual(str(self.w), '1023 B')
+ self.w.update({ 'foo': 1023 })
+ self.assertEqual(self.w.render(0), '1023 B')
def test_formats_1024_bytes_correctly(self):
- self.w.update({ 'foo': 1024 }, 999)
- self.assertEqual(str(self.w), '1.0 KiB')
+ self.w.update({ 'foo': 1024 })
+ self.assertEqual(self.w.render(0), '1.0 KiB')
def test_formats_1_MiB_bytes_correctly(self):
- self.w.update({ 'foo': 1024**2 }, 999)
- self.assertEqual(str(self.w), '1.00 MiB')
+ self.w.update({ 'foo': 1024**2 })
+ self.assertEqual(self.w.render(0), '1.00 MiB')
def test_formats_1_GiB_bytes_correctly(self):
- self.w.update({ 'foo': 1024**3 }, 999)
- self.assertEqual(str(self.w), '1.00 GiB')
+ self.w.update({ 'foo': 1024**3 })
+ self.assertEqual(self.w.render(0), '1.00 GiB')
def test_formats_1_TiB_bytes_correctly(self):
- self.w.update({ 'foo': 1024**4 }, 999)
- self.assertEqual(str(self.w), '1.00 TiB')
+ self.w.update({ 'foo': 1024**4 })
+ self.assertEqual(self.w.render(0), '1.00 TiB')
diff --git a/ttystatus/bytespeed.py b/ttystatus/bytespeed.py
index 7f54b86..3331069 100644
--- a/ttystatus/bytespeed.py
+++ b/ttystatus/bytespeed.py
@@ -22,10 +22,11 @@ import ttystatus
class ByteSpeed(ttystatus.Widget):
'''Display data size in bytes, KiB, etc.'''
+
+ static_width = False
def __init__(self, name):
self.name = name
- self.interesting_keys = [name]
self._bytes = 0
self._started = None
@@ -34,7 +35,7 @@ class ByteSpeed(ttystatus.Widget):
return time.time()
- def format(self):
+ def render(self, width):
units = (
(1024**4, 2, 'TiB/s'),
(1024**3, 2, 'GiB/s'),
@@ -55,7 +56,7 @@ class ByteSpeed(ttystatus.Widget):
unit)
return '%.0f B/s' % speed
- def update(self, master, width):
+ def update(self, master):
if self._started is None:
self._started = self.now()
self._bytes = master[self.name]
diff --git a/ttystatus/bytespeed_tests.py b/ttystatus/bytespeed_tests.py
index 4c481fe..9bcc1e0 100644
--- a/ttystatus/bytespeed_tests.py
+++ b/ttystatus/bytespeed_tests.py
@@ -24,31 +24,34 @@ class ByteSpeedTests(unittest.TestCase):
def setUp(self):
self.w = ttystatus.ByteSpeed('foo')
+ def test_is_not_static_width(self):
+ self.assertFalse(self.w.static_width)
+
def test_formats_zero_speed_without_update(self):
- self.assertEqual(str(self.w), '0 B/s')
+ self.assertEqual(self.w.render(0), '0 B/s')
def test_formats_zero_bytes_correctly(self):
- self.w.update({ 'foo': 0 }, 999)
- self.assertEqual(str(self.w), '0 B/s')
+ self.w.update({ 'foo': 0 })
+ self.assertEqual(self.w.render(0), '0 B/s')
def test_formats_one_byte_per_second_correctly(self):
self.w.now = lambda: 1
- self.w.update({ 'foo': 0 }, 999)
+ self.w.update({ 'foo': 0 })
self.w.now = lambda: 2
- self.w.update({ 'foo': 1 }, 999)
- self.assertEqual(str(self.w), '1 B/s')
+ self.w.update({ 'foo': 1 })
+ self.assertEqual(self.w.render(0), '1 B/s')
def test_formats_ten_bytes_per_second_correctly(self):
self.w.now = lambda: 1
- self.w.update({ 'foo': 0 }, 999)
+ self.w.update({ 'foo': 0 })
self.w.now = lambda: 11
- self.w.update({ 'foo': 100 }, 999)
- self.assertEqual(str(self.w), '10 B/s')
+ self.w.update({ 'foo': 100 })
+ self.assertEqual(self.w.render(0), '10 B/s')
def test_formats_ten_tibs_per_second_correctly(self):
self.w.now = lambda: 1
- self.w.update({ 'foo': 0 }, 999)
+ self.w.update({ 'foo': 0 })
self.w.now = lambda: 2
- self.w.update({ 'foo': 10 * 1024**4 }, 999)
- self.assertEqual(str(self.w), '10.00 TiB/s')
+ self.w.update({ 'foo': 10 * 1024**4 })
+ self.assertEqual(self.w.render(0), '10.00 TiB/s')
diff --git a/ttystatus/counter.py b/ttystatus/counter.py
index 5d3e11f..d37ed90 100644
--- a/ttystatus/counter.py
+++ b/ttystatus/counter.py
@@ -21,16 +21,17 @@ class Counter(ttystatus.Widget):
'''Display a count of how many times a value has changed.'''
+ static_width = False
+
def __init__(self, name):
self.name = name
self.prev = None
self.count = 0
- self.interesting_keys = [name]
- def format(self):
+ def render(self, width):
return str(self.count)
- def update(self, master, width):
+ def update(self, master):
if master[self.name] != self.prev:
self.prev = master[self.name]
self.count += 1
diff --git a/ttystatus/counter_tests.py b/ttystatus/counter_tests.py
index 866bcbd..cadadea 100644
--- a/ttystatus/counter_tests.py
+++ b/ttystatus/counter_tests.py
@@ -24,20 +24,23 @@ class CounterTests(unittest.TestCase):
def setUp(self):
self.w = ttystatus.Counter('foo')
+ def test_is_not_static_width(self):
+ self.assertFalse(self.w.static_width)
+
def test_counts_zero_initially(self):
- self.assertEqual(str(self.w), '0')
+ self.assertEqual(self.w.render(0), '0')
def test_counts_one_change(self):
- self.w.update({ 'foo': 'a' }, 999)
- self.assertEqual(str(self.w), '1')
+ self.w.update({ 'foo': 'a' })
+ self.assertEqual(self.w.render(0), '1')
def test_counts_two_changes(self):
- self.w.update({ 'foo': 'a' }, 999)
- self.w.update({ 'foo': 'b' }, 999)
- self.assertEqual(str(self.w), '2')
+ self.w.update({ 'foo': 'a' })
+ self.w.update({ 'foo': 'b' })
+ self.assertEqual(self.w.render(0), '2')
def test_does_not_count_if_value_does_not_change(self):
- self.w.update({ 'foo': 'a' }, 999)
- self.w.update({ 'foo': 'a' }, 999)
- self.assertEqual(str(self.w), '1')
+ self.w.update({ 'foo': 'a' })
+ self.w.update({ 'foo': 'a' })
+ self.assertEqual(self.w.render(0), '1')
diff --git a/ttystatus/elapsed.py b/ttystatus/elapsed.py
index 2032e6e..cb75722 100644
--- a/ttystatus/elapsed.py
+++ b/ttystatus/elapsed.py
@@ -25,14 +25,13 @@ class ElapsedTime(ttystatus.Widget):
def __init__(self):
self.started = None
- self.interesting_keys = None
self.secs = 0
def get_time(self): # pragma: no cover
'''Wrapper around time.time() for unit tests to override.'''
return time.time()
- def format(self):
+ def render(self, width):
secs = self.secs
hours = secs / 3600
secs %= 3600
@@ -40,7 +39,7 @@ class ElapsedTime(ttystatus.Widget):
secs %= 60
return '%02dh%02dm%02ds' % (hours, mins, secs)
- def update(self, master, width):
+ def update(self, master):
if self.started is None:
self.started = self.get_time()
self.secs = self.get_time() - self.started
diff --git a/ttystatus/elapsed_tests.py b/ttystatus/elapsed_tests.py
index dc2bfe7..b87d57c 100644
--- a/ttystatus/elapsed_tests.py
+++ b/ttystatus/elapsed_tests.py
@@ -24,18 +24,21 @@ class ElapsedtimeTests(unittest.TestCase):
def setUp(self):
self.w = ttystatus.ElapsedTime()
+ def test_is_static_width(self):
+ self.assertTrue(self.w.static_width)
+
def test_shows_zero_initially(self):
- self.assertEqual(str(self.w), '00h00m00s')
+ self.assertEqual(self.w.render(0), '00h00m00s')
def test_shows_zero_after_first_update(self):
self.w.get_time = lambda: 1
- self.w.update({}, 999)
- self.assertEqual(str(self.w), '00h00m00s')
+ self.w.update({})
+ self.assertEqual(self.w.render(0), '00h00m00s')
def test_shows_one_one_one_after_second_update(self):
self.w.get_time = lambda: 0
- self.w.update({}, 999)
+ self.w.update({})
self.w.get_time = lambda: 60*60 + 60 + 1
- self.w.update({}, 999)
- self.assertEqual(str(self.w), '01h01m01s')
+ self.w.update({})
+ self.assertEqual(self.w.render(0), '01h01m01s')
diff --git a/ttystatus/fmt_tests.py b/ttystatus/fmt_tests.py
index af39948..310f131 100644
--- a/ttystatus/fmt_tests.py
+++ b/ttystatus/fmt_tests.py
@@ -32,13 +32,13 @@ class FormatTests(unittest.TestCase):
x = ttystatus.fmt.parse('hello, world')
self.assertEqual(len(x), 1)
self.assertEqual(type(x[0]), ttystatus.Literal)
- self.assertEqual(str(x[0]), 'hello, world')
+ self.assertEqual(x[0].render(0), 'hello, world')
def test_parses_escaped_pecent(self):
x = ttystatus.fmt.parse('%%')
self.assertEqual(len(x), 1)
self.assertEqual(type(x[0]), ttystatus.Literal)
- self.assertEqual(str(x[0]), '%')
+ self.assertEqual(x[0].render(0), '%')
def test_parses_parameterless_widget(self):
x = ttystatus.fmt.parse('%ElapsedTime()')
@@ -60,12 +60,12 @@ class FormatTests(unittest.TestCase):
self.assertEqual(len(x), 4)
self.assertEqual(type(x[0]), ttystatus.Literal)
- self.assertEqual(str(x[0]), 'hello, ')
+ self.assertEqual(x[0].render(0), 'hello, ')
self.assertEqual(type(x[1]), ttystatus.String)
self.assertEqual(type(x[2]), ttystatus.Literal)
- self.assertEqual(str(x[2]), ': ')
+ self.assertEqual(x[2].render(0), ': ')
self.assertEqual(type(x[3]), ttystatus.ElapsedTime)
diff --git a/ttystatus/index.py b/ttystatus/index.py
index 941165a..0158037 100644
--- a/ttystatus/index.py
+++ b/ttystatus/index.py
@@ -21,21 +21,22 @@ class Index(ttystatus.Widget):
'''Display the position of a value in a list of values.'''
+ static_width = False
+
def __init__(self, name, listname):
self.name = name
self.listname = listname
- self.interesting_keys = [name, listname]
self.value = None
self.listvalue = []
- def format(self):
+ def render(self, render):
try:
index = self.listvalue.index(self.value) + 1
except ValueError:
index = 0
return '%d/%d' % (index, len(self.listvalue))
- def update(self, master, width):
+ def update(self, master):
self.value = master[self.name]
self.listvalue = master[self.listname]
diff --git a/ttystatus/index_tests.py b/ttystatus/index_tests.py
index 3c91903..ccc4d4c 100644
--- a/ttystatus/index_tests.py
+++ b/ttystatus/index_tests.py
@@ -24,14 +24,17 @@ class IndexTests(unittest.TestCase):
def setUp(self):
self.w = ttystatus.Index('foo', 'foos')
+ def test_is_not_static_width(self):
+ self.assertFalse(self.w.static_width)
+
def test_is_zero_initially(self):
- self.assertEqual(str(self.w), '0/0')
+ self.assertEqual(self.w.render(0), '0/0')
def test_gets_index_right(self):
- self.w.update({ 'foo': 'x', 'foos': ['a', 'x', 'b'] }, 999)
- self.assertEqual(str(self.w), '2/3')
+ self.w.update({ 'foo': 'x', 'foos': ['a', 'x', 'b'] })
+ self.assertEqual(self.w.render(0), '2/3')
def test_handles_value_not_in_list(self):
- self.w.update({ 'foo': 'xxx', 'foos': ['a', 'x', 'b'] }, 999)
- self.assertEqual(str(self.w), '0/3')
+ self.w.update({ 'foo': 'xxx', 'foos': ['a', 'x', 'b'] })
+ self.assertEqual(self.w.render(0), '0/3')
diff --git a/ttystatus/integer.py b/ttystatus/integer.py
index b86dbe0..7995bee 100644
--- a/ttystatus/integer.py
+++ b/ttystatus/integer.py
@@ -21,17 +21,18 @@ class Integer(ttystatus.Widget):
'''Display a value as an integer.'''
+ static_width = False
+
def __init__(self, key):
self._key = key
- self.interesting_keys = [key]
- self.value = '#'
+ self.value = None
- def format(self):
- return self.value
-
- def update(self, master, width):
+ def render(self, width):
try:
- self.value = str(int(master[self._key]))
- except ValueError:
- self.value = '#'
+ return str(int(self.value))
+ except (TypeError, ValueError):
+ return '#'
+
+ def update(self, master):
+ self.value = master[self._key]
diff --git a/ttystatus/integer_tests.py b/ttystatus/integer_tests.py
index a3f04fe..e064621 100644
--- a/ttystatus/integer_tests.py
+++ b/ttystatus/integer_tests.py
@@ -24,14 +24,17 @@ class IntegerTests(unittest.TestCase):
def setUp(self):
self.w = ttystatus.Integer('foo')
+ def test_is_not_static_width(self):
+ self.assertFalse(self.w.static_width)
+
def test_is_error_initially(self):
- self.assertEqual(str(self.w), '#')
+ self.assertEqual(self.w.render(0), '#')
def test_updates(self):
- self.w.update({'foo': 123}, 999)
- self.assertEqual(str(self.w), '123')
+ self.w.update({'foo': 123})
+ self.assertEqual(self.w.render(0), '123')
def test_becomes_error_symbol_if_value_is_not_integer(self):
- self.w.update({'foo': 'bar'}, 999)
- self.assertEqual(str(self.w), '#')
+ self.w.update({'foo': 'bar'})
+ self.assertEqual(self.w.render(0), '#')
diff --git a/ttystatus/literal.py b/ttystatus/literal.py
index 5c41b69..cdeb4d7 100644
--- a/ttystatus/literal.py
+++ b/ttystatus/literal.py
@@ -23,7 +23,6 @@ class Literal(ttystatus.Widget):
def __init__(self, string):
self.value = string
- self.interesting_keys = []
- def format(self):
+ def render(self, width):
return self.value
diff --git a/ttystatus/literal_tests.py b/ttystatus/literal_tests.py
index 969439d..b1e4537 100644
--- a/ttystatus/literal_tests.py
+++ b/ttystatus/literal_tests.py
@@ -21,6 +21,11 @@ import ttystatus
class LiteralTests(unittest.TestCase):
+ def setUp(self):
+ self.w = ttystatus.Literal('foo')
+
+ def test_is_static_width(self):
+ self.assertTrue(self.w.static_width)
+
def test_sets_value_correctly(self):
- literal = ttystatus.Literal('foo')
- self.assertEqual(str(literal), 'foo')
+ self.assertEqual(self.w.render(0), 'foo')
diff --git a/ttystatus/pathname.py b/ttystatus/pathname.py
index 0f5fb43..0fbdbd7 100644
--- a/ttystatus/pathname.py
+++ b/ttystatus/pathname.py
@@ -24,24 +24,16 @@ class Pathname(ttystatus.Widget):
If it won't fit completely, truncate from the beginning of the string.
'''
+
+ static_width = False
def __init__(self, key):
self._key = key
- self.interesting_keys = [key]
self.pathname = ''
- self.width = 0
-
- def format(self):
- v = self.pathname
- if len(v) > self.width:
- ellipsis = '...'
- if len(ellipsis) < self.width:
- v = ellipsis + v[-(self.width - len(ellipsis)):]
- else:
- v = v[-self.width:]
- return v
+
+ def render(self, width):
+ return self.pathname[-width:]
- def update(self, master, width):
+ def update(self, master):
self.pathname = master.get(self._key, '')
- self.width = width
diff --git a/ttystatus/pathname_tests.py b/ttystatus/pathname_tests.py
index f0c54d5..70d6c1e 100644
--- a/ttystatus/pathname_tests.py
+++ b/ttystatus/pathname_tests.py
@@ -24,30 +24,21 @@ class PathnameTests(unittest.TestCase):
def setUp(self):
self.w = ttystatus.Pathname('foo')
+ def test_is_not_static_width(self):
+ self.assertFalse(self.w.static_width)
+
def test_is_empty_initially(self):
- self.assertEqual(str(self.w), '')
+ self.assertEqual(self.w.render(10), '')
def test_updates(self):
- self.w.update({'foo': 'bar'}, 999)
- self.assertEqual(str(self.w), 'bar')
+ self.w.update({'foo': 'bar'})
+ self.assertEqual(self.w.render(10), 'bar')
def test_handles_update_to_other_value(self):
- self.w.update({'other': 1}, 999)
- self.assertEqual(str(self.w), '')
-
+ self.w.update({'other': 1})
+ self.assertEqual(self.w.render(10), '')
+
def test_truncates_from_beginning(self):
- self.w.update({'foo': 'foobar'}, 5)
- self.assertEqual(str(self.w), '...ar')
-
- def test_does_not_truncate_for_exact_fit(self):
- self.w.update({'foo': 'foobar'}, 6)
- self.assertEqual(str(self.w), 'foobar')
-
- def test_does_not_add_ellipsis_if_it_will_not_fit(self):
- self.w.update({'foo': 'foobar'}, 3)
- self.assertEqual(str(self.w), 'bar')
-
- def test_adds_ellipsis_if_it_just_fits(self):
- self.w.update({'foo': 'foobar'}, 4)
- self.assertEqual(str(self.w), '...r')
+ self.w.update({'foo': '/this/is/a/path'})
+ self.assertEqual(self.w.render(6), 'a/path')
diff --git a/ttystatus/percent.py b/ttystatus/percent.py
index af2935c..1a60c23 100644
--- a/ttystatus/percent.py
+++ b/ttystatus/percent.py
@@ -20,6 +20,8 @@ import ttystatus
class PercentDone(ttystatus.Widget):
'''Display percent of task done.'''
+
+ static_width = False
def __init__(self, done_name, total_name, decimals=0):
self.done_name = done_name
@@ -27,9 +29,8 @@ class PercentDone(ttystatus.Widget):
self.decimals = decimals
self.done = 0
self.total = 1
- self.interesting_keys = [done_name, total_name]
- def format(self):
+ def render(self, render):
try:
done = float(self.done)
total = float(self.total)
@@ -40,6 +41,6 @@ class PercentDone(ttystatus.Widget):
total = 1
return '%.*f %%' % (self.decimals, 100.0 * done / total)
- def update(self, master, width):
+ def update(self, master):
self.done = master[self.done_name]
self.total = master[self.total_name]
diff --git a/ttystatus/percent_tests.py b/ttystatus/percent_tests.py
index 29c7cb4..9ffe0b9 100644
--- a/ttystatus/percent_tests.py
+++ b/ttystatus/percent_tests.py
@@ -24,18 +24,21 @@ class PercentDoneTests(unittest.TestCase):
def setUp(self):
self.w = ttystatus.PercentDone('done', 'total', decimals=1)
+ def test_is_not_static_width(self):
+ self.assertFalse(self.w.static_width)
+
def test_shows_zero_value_initially(self):
- self.assertEqual(str(self.w), '0.0 %')
+ self.assertEqual(self.w.render(0), '0.0 %')
def test_sets_value(self):
- self.w.update({ 'done': 50, 'total': 100 }, 999)
- self.assertEqual(str(self.w), '50.0 %')
+ self.w.update({ 'done': 50, 'total': 100 })
+ self.assertEqual(self.w.render(0), '50.0 %')
def test_handles_empty_strings_as_values(self):
- self.w.update({ 'done': '', 'total': '' }, 999)
- self.assertEqual(str(self.w), '0.0 %')
+ self.w.update({ 'done': '', 'total': '' })
+ self.assertEqual(self.w.render(0), '0.0 %')
def test_handles_zero_total(self):
- self.w.update({ 'done': 0, 'total': 0 }, 999)
- self.assertEqual(str(self.w), '0.0 %')
+ self.w.update({ 'done': 0, 'total': 0 })
+ self.assertEqual(self.w.render(0), '0.0 %')
diff --git a/ttystatus/progressbar.py b/ttystatus/progressbar.py
index e8549f2..cd0f1a3 100644
--- a/ttystatus/progressbar.py
+++ b/ttystatus/progressbar.py
@@ -20,16 +20,16 @@ import ttystatus
class ProgressBar(ttystatus.Widget):
'''Display a progress bar.'''
+
+ static_width = False
def __init__(self, done_name, total_name):
self.done_name = done_name
self.total_name = total_name
- self.interesting_keys = [done_name, total_name]
self.done = 0
self.total = 1
- self.width = 0
- def format(self):
+ def render(self, width):
try:
done = float(self.done)
total = float(self.total)
@@ -40,12 +40,10 @@ class ProgressBar(ttystatus.Widget):
fraction = 0
else:
fraction = done / total
- n_stars = int(round(fraction * self.width))
- n_dashes = int(self.width - n_stars)
+ n_stars = int(round(fraction * width))
+ n_dashes = int(width - n_stars)
return ('#' * n_stars) + ('-' * n_dashes)
- def update(self, master, width):
+ def update(self, master):
self.done = master[self.done_name]
self.total = master[self.total_name]
- self.width = width
-
diff --git a/ttystatus/progressbar_tests.py b/ttystatus/progressbar_tests.py
index 0e3bf5a..a5dd425 100644
--- a/ttystatus/progressbar_tests.py
+++ b/ttystatus/progressbar_tests.py
@@ -23,39 +23,43 @@ class ProgressBarTests(unittest.TestCase):
def setUp(self):
self.w = ttystatus.ProgressBar('done', 'total')
+ self.width = 10
+
+ def test_is_not_static_width(self):
+ self.assertFalse(self.w.static_width)
def test_sets_initial_value_to_empty(self):
- self.assertEqual(str(self.w), '')
+ self.assertEqual(self.w.render(self.width), '-' * 10)
def test_shows_zero_percent_for_empty_string_total(self):
- self.w.update({ 'done': 1, 'total': '' }, 10)
- self.assertEqual(str(self.w), '-' * 10)
+ self.w.update({ 'done': 1, 'total': '' })
+ self.assertEqual(self.w.render(self.width), '-' * 10)
def test_shows_zero_percent_for_zero_total(self):
- self.w.update({ 'done': 1, 'total': 0 }, 10)
- self.assertEqual(str(self.w), '-' * 10)
+ self.w.update({ 'done': 1, 'total': 0 })
+ self.assertEqual(self.w.render(self.width), '-' * 10)
def test_shows_zero_percent_correctly(self):
- self.w.update({ 'done': 0, 'total': 100 }, 10)
- self.assertEqual(str(self.w), '-' * 10)
+ self.w.update({ 'done': 0, 'total': 100 })
+ self.assertEqual(self.w.render(self.width), '-' * 10)
def test_shows_one_percent_correctly(self):
- self.w.update({ 'done': 1, 'total': 100 }, 10)
- self.assertEqual(str(self.w), '-' * 10)
+ self.w.update({ 'done': 1, 'total': 100 })
+ self.assertEqual(self.w.render(self.width), '-' * 10)
def test_shows_ten_percent_correctly(self):
- self.w.update({ 'done': 10, 'total': 100 }, 10)
- self.assertEqual(str(self.w), '#' + '-' * 9)
+ self.w.update({ 'done': 10, 'total': 100 })
+ self.assertEqual(self.w.render(self.width), '#' + '-' * 9)
def test_shows_ninety_percent_correctly(self):
- self.w.update({ 'done': 90, 'total': 100 }, 10)
- self.assertEqual(str(self.w), '#' * 9 + '-')
+ self.w.update({ 'done': 90, 'total': 100 })
+ self.assertEqual(self.w.render(self.width), '#' * 9 + '-')
def test_shows_ninety_ine_percent_correctly(self):
- self.w.update({ 'done': 99, 'total': 100 }, 10)
- self.assertEqual(str(self.w), '#' * 10)
+ self.w.update({ 'done': 99, 'total': 100 })
+ self.assertEqual(self.w.render(self.width), '#' * 10)
def test_shows_one_hundred_percent_correctly(self):
- self.w.update({ 'done': 100, 'total': 100 }, 10)
- self.assertEqual(str(self.w), '#' * 10)
+ self.w.update({ 'done': 100, 'total': 100 })
+ self.assertEqual(self.w.render(self.width), '#' * 10)
diff --git a/ttystatus/remtime.py b/ttystatus/remtime.py
index fd0d07c..cbf55bd 100644
--- a/ttystatus/remtime.py
+++ b/ttystatus/remtime.py
@@ -28,7 +28,6 @@ class RemainingTime(ttystatus.Widget):
self.total_name = total_name
self.started = None
self.default = '--h--m--s'
- self.interesting_keys = [done_name, total_name]
self.done = 0
self.total = 1
@@ -42,7 +41,7 @@ class RemainingTime(ttystatus.Widget):
return time.time()
- def format(self):
+ def render(self, render):
if self.started is None:
self.started = self.get_time()
duration = self.get_time() - self.started
@@ -58,6 +57,6 @@ class RemainingTime(ttystatus.Widget):
return '%02dh%02dm%02ds' % (hours, mins, secs)
return self.default
- def update(self, master, width):
+ def update(self, master):
self.done = master[self.done_name]
self.total = master[self.total_name]
diff --git a/ttystatus/remtime_tests.py b/ttystatus/remtime_tests.py
index 2c67345..90afd42 100644
--- a/ttystatus/remtime_tests.py
+++ b/ttystatus/remtime_tests.py
@@ -25,31 +25,34 @@ class RemainingTimeTests(unittest.TestCase):
self.w = ttystatus.RemainingTime('done', 'total')
self.w.get_time = lambda: 0.0
+ def test_is_static_width(self):
+ self.assertTrue(self.w.static_width)
+
def test_is_dashes_initially(self):
- self.assertEqual(str(self.w), '--h--m--s')
+ self.assertEqual(self.w.render(0), '--h--m--s')
def test_estimates_and_formats_correctly(self):
- self.assertEqual(str(self.w), '--h--m--s')
- self.w.update({ 'done': 0, 'total': 100 }, 999)
+ self.assertEqual(self.w.render(0), '--h--m--s')
+ self.w.update({ 'done': 0, 'total': 100 })
self.w.get_time = lambda: 5.0
- self.w.update({ 'done': 5, 'total': 100 }, 999)
- self.assertEqual(str(self.w), '00h01m35s')
+ self.w.update({ 'done': 5, 'total': 100 })
+ self.assertEqual(self.w.render(0), '00h01m35s')
self.w.get_time = lambda: 10.0
- self.w.update({ 'done': 5, 'total': 100 }, 999)
- self.assertEqual(str(self.w), '00h03m10s')
+ self.w.update({ 'done': 5, 'total': 100 })
+ self.assertEqual(self.w.render(0), '00h03m10s')
self.w.get_time = lambda: 20.0
- self.w.update({ 'done': 80, 'total': 100 }, 999)
- self.assertEqual(str(self.w), '00h00m05s')
+ self.w.update({ 'done': 80, 'total': 100 })
+ self.assertEqual(self.w.render(0), '00h00m05s')
def test_handles_zero_speed(self):
- self.w.update({ 'done': 0, 'total': 100 }, 999)
+ self.w.update({ 'done': 0, 'total': 100 })
self.w.get_time = lambda: 5.0
- self.w.update({ 'done': 0, 'total': 100 }, 999)
- self.assertEqual(str(self.w), '--h--m--s')
+ self.w.update({ 'done': 0, 'total': 100 })
+ self.assertEqual(self.w.render(0), '--h--m--s')
def test_handles_empty_strings_for_done_and_total(self):
- self.w.update({ 'done': '', 'total': '' }, 999)
+ self.w.update({ 'done': '', 'total': '' })
self.w.get_time = lambda: 5.0
- self.w.update({ 'done': '', 'total': '' }, 999)
- self.assertEqual(str(self.w), '--h--m--s')
+ self.w.update({ 'done': '', 'total': '' })
+ self.assertEqual(self.w.render(0), '--h--m--s')
diff --git a/ttystatus/status.py b/ttystatus/status.py
index 25fd1c1..b98fecd 100644
--- a/ttystatus/status.py
+++ b/ttystatus/status.py
@@ -39,11 +39,6 @@ class TerminalStatus(object):
def add(self, widget):
'''Add a new widget to the status display.'''
self._widgets.append(widget)
- if widget.interesting_keys is None:
- self._wildcards += [widget]
- else:
- for key in widget.interesting_keys:
- self._interests[key] = self._interests.get(key, []) + [widget]
def format(self, format_string):
'''Add new widgets based on format string.
@@ -61,9 +56,6 @@ class TerminalStatus(object):
'''Remove all widgets.'''
self._widgets = []
self._values = dict()
- self._interests = dict()
- self._wildcards = list()
- self._latest_width = None
self._m.clear()
def __getitem__(self, key):
@@ -77,17 +69,34 @@ class TerminalStatus(object):
def __setitem__(self, key, value):
'''Set value for key.'''
self._values[key] = value
+ for w in self._widgets:
+ w.update(self)
if self._m.time_to_write():
- self._format()
+ self._write()
+
+ def _render(self):
+ '''Render current state of all widgets.'''
+
+ remaining = self._m.width
+
+ texts = [None] * len(self._widgets)
+
+ for i, w in enumerate(self._widgets):
+ if w.static_width:
+ texts[i] = w.render(0)
+ remaining -= len(texts[i])
+
+ for i, w in enumerate(self._widgets):
+ if not w.static_width:
+ texts[i] = w.render(remaining)
+ remaining -= len(texts[i])
+
+ return (''.join(texts))[:self._m.width]
+
+ def _write(self):
+ '''Render and output current state of all widgets.'''
+ self._m.write(self._render())
- def _format(self):
- '''Format and output all widgets.'''
- width = self._m.width
- for w in self._widgets:
- w.update(self, width)
- width -= len(str(w))
- self._m.write(''.join(str(w) for w in self._widgets))
-
def increase(self, key, delta):
'''Increase value for a key by a given amount.'''
self[key] = (self[key] or 0) + delta
@@ -102,7 +111,7 @@ class TerminalStatus(object):
def finish(self):
'''Finish status display.'''
- self._format()
+ self._write()
self._m.finish()
def disable(self):
diff --git a/ttystatus/status_tests.py b/ttystatus/status_tests.py
index 3583028..ef8959a 100644
--- a/ttystatus/status_tests.py
+++ b/ttystatus/status_tests.py
@@ -63,22 +63,6 @@ class TerminalStatusTests(unittest.TestCase):
self.ts.add(w)
self.assertEqual(self.ts._widgets, [w])
- def test_adds_widget_as_interested_in_keys(self):
- class W(ttystatus.Widget):
- def __init__(self):
- self.interesting_keys = ['foo']
- w = W()
- self.ts.add(w)
- self.assert_(w in self.ts._interests['foo'])
-
- def test_adds_widget_to_wildcards(self):
- class W(ttystatus.Widget):
- def __init__(self):
- self.interesting_keys = None
- w = W()
- self.ts.add(w)
- self.assert_(w in self.ts._wildcards)
-
def test_adds_widgets_from_format_string(self):
self.ts.format('hello, %String(name)')
self.assertEqual(len(self.ts._widgets), 2)
@@ -110,9 +94,9 @@ class TerminalStatusTests(unittest.TestCase):
def test_updates_widgets_when_value_is_set(self):
w = ttystatus.String('foo')
self.ts.add(w)
- self.assertEqual(str(w), '')
+ self.assertEqual(w.render(0), '')
self.ts['foo'] = 'bar'
- self.assertEqual(str(w), 'bar')
+ self.assertEqual(w.render(0), 'bar')
def test_increases_value(self):
self.ts['foo'] = 10
@@ -138,3 +122,39 @@ class TerminalStatusTests(unittest.TestCase):
self.ts.enable()
self.assert_(self.ts._m.enabled)
+ def test_counts_correctly_even_without_rendering(self):
+ w = ttystatus.Counter('value')
+ n = 42
+ self.ts.add(w)
+ for i in range(n):
+ self.ts['value'] = i
+ self.assertEqual(w.render(0), str(n))
+
+ def test_renders_everything_when_there_is_space(self):
+ w1 = ttystatus.Literal('foo')
+ w2 = ttystatus.ProgressBar('done', 'total')
+ self.ts.add(w1)
+ self.ts.add(w2)
+ text = self.ts._render()
+ self.assertEqual(len(text), self.ts._m.width)
+
+ def test_renders_from_beginning_if_there_is_not_enough_space(self):
+ w1 = ttystatus.Literal('foo')
+ w2 = ttystatus.Literal('bar')
+ self.ts.add(w1)
+ self.ts.add(w2)
+ self.ts._m.width = 4
+ text = self.ts._render()
+ self.assertEqual(text, 'foob')
+
+ def test_renders_variable_size_width_according_to_space_keep_static(self):
+ w1 = ttystatus.Literal('foo')
+ w2 = ttystatus.ProgressBar('done', 'total')
+ w3 = ttystatus.Literal('bar')
+ self.ts.add(w1)
+ self.ts.add(w2)
+ self.ts.add(w3)
+ self.ts._m.width = 9
+ text = self.ts._render()
+ self.assertEqual(text, 'foo---bar')
+
diff --git a/ttystatus/string.py b/ttystatus/string.py
index 32a76ea..92fbf88 100644
--- a/ttystatus/string.py
+++ b/ttystatus/string.py
@@ -20,14 +20,15 @@ import ttystatus
class String(ttystatus.Widget):
'''Display a value as a string.'''
+
+ static_width = False
def __init__(self, key):
self._key = key
- self.interesting_keys = [key]
self.value = ''
- def format(self):
+ def render(self, render):
return str(self.value)
- def update(self, master, width):
+ def update(self, master):
self.value = master[self._key]
diff --git a/ttystatus/string_tests.py b/ttystatus/string_tests.py
index 39f480a..faa266b 100644
--- a/ttystatus/string_tests.py
+++ b/ttystatus/string_tests.py
@@ -24,13 +24,16 @@ class StringTests(unittest.TestCase):
def setUp(self):
self.s = ttystatus.String('foo')
+ def test_is_not_static_width(self):
+ self.assertFalse(self.s.static_width)
+
def test_is_empty_initially(self):
- self.assertEqual(str(self.s), '')
+ self.assertEqual(self.s.render(0), '')
def test_updates(self):
- self.s.update({'foo': 'bar'}, 999)
- self.assertEqual(str(self.s), 'bar')
+ self.s.update({'foo': 'bar'})
+ self.assertEqual(self.s.render(0), 'bar')
def test_handles_non_string_value(self):
- self.s.update({'foo': 123}, 999)
- self.assertEqual(str(self.s), '123')
+ self.s.update({'foo': 123})
+ self.assertEqual(self.s.render(0), '123')
diff --git a/ttystatus/widget.py b/ttystatus/widget.py
index 0f03365..9d14683 100644
--- a/ttystatus/widget.py
+++ b/ttystatus/widget.py
@@ -18,37 +18,39 @@ class Widget(object):
'''Base class for ttystatus widgets.
- Widgets are responsible for formatting part of the output. They
- get a value or values either directly from the user, or from the
- master TerminalStatus widget. They return the formatted string
- via __str__.
+ Widgets display stuff on screen. The value may depend on data provided
+ by the user (at creation time), or may be computed from one or more
+ values in the TerminalStatus object to which the widget object belongs.
- A widget's value may be derived from values stored in the TerminalStatus
- widget (called master). Each such value has a key. Computing a widget's
- value is a two-step process: when the values associated with keys
- are updated, the widget's update() method is called to notify it of
- this. update() may compute intermediate values, such as maintain a
- counter of the number of changes. It should avoid costly operations
- that are only needed when the widget's formatted value is needed.
- Those should go into the format() method instead. Thus, update() would
- update a counter, format() would create a string representing the
- counter.
+ There are two steps:
- This is necessary because actual on-screen updates only happen
- every so often, not every time a value in the master changes, and
- often the string formatting part is expensive.
+ * the widget `update` method is called by TerminalStatus whenever
+ any of the values change
+ * the widget `render` method is called by TerminalStatus when it is
+ time to display things
+
+ Widgets may have a static size, or their size may vary. The
+ ``static_width`` property reveals this. This affects rendering:
+ static sized widgets are rendered at their one static size; variable
+ sized widgets are shrunk, if necessary, to make everything fit into
+ the available space. If it's not possible to shrink enough, widgets
+ are rendered from beginning until the space is full: variable sized
+ widgets are rendered as small as possible in this case.
+
+ '''
- Widgets must have an attribute 'interesting_keys', listing the
- keys it is interested in.
+ static_width = True
- '''
-
def __str__(self):
- '''Return current value to be displayed for this widget.'''
- return self.format()
-
- def format(self):
+ raise NotImplementedError()
+
+ def render(self, width):
'''Format the current value.
+
+ ``width`` is the available width for the widget. It need not use
+ all of it. If it's not possible for the widget to render itself
+ small enough to fit into the given width, it may return a larger
+ string, but the caller will probably truncate it.
This will be called only when the value actually needs to be
formatted.
@@ -57,11 +59,6 @@ class Widget(object):
return ''
- def update(self, master, width):
- '''Update displayed value for widget, from values in master.
-
- 'width' gives the width for which the widget should aim to fit.
- It is OK if it does not: for some widgets there is no way to
- adjust to a smaller size.
-
- '''
+ def update(self, terminal_status):
+ '''React to changes in values stored in a TerminalStatus.'''
+