diff options
29 files changed, 303 insertions, 250 deletions
@@ -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.''' + |