[tor-commits] [nyx/master] Rewrite log panel title handling

atagar at torproject.org atagar at torproject.org
Tue May 5 05:42:06 UTC 2015


commit a909b71b1d8c870d88914b049b87fec2bb7608b2
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Apr 26 22:01:41 2015 -0700

    Rewrite log panel title handling
    
    Moved the tricky bit to a helper, now with tests.
---
 nyx/log_panel.py                    |  152 +++--------------------------------
 nyx/util/log.py                     |   69 ++++++++++++++++
 test/util/log/condense_runlevels.py |   13 +++
 3 files changed, 95 insertions(+), 139 deletions(-)

diff --git a/nyx/log_panel.py b/nyx/log_panel.py
index 2038a69..3e83de5 100644
--- a/nyx/log_panel.py
+++ b/nyx/log_panel.py
@@ -21,8 +21,8 @@ import nyx.arguments
 import nyx.popups
 
 from nyx import __version__
-from nyx.util import panel, tor_controller, ui_tools
-from nyx.util.log import LogGroup, LogEntry, read_tor_log, days_since
+from nyx.util import join, panel, tor_controller, ui_tools
+from nyx.util.log import TOR_RUNLEVELS, LogGroup, LogEntry, read_tor_log, condense_runlevels, days_since
 
 ENTRY_INDENT = 2  # spaces an entry's message is indented after the first line
 
@@ -53,8 +53,6 @@ CONFIG = conf.config_dict('nyx', {
   'attr.log_color': {},
 }, conf_handler)
 
-DUPLICATE_MSG = ' [%i duplicate%s hidden]'
-
 # The height of the drawn content is estimated based on the last time we redrew
 # the panel. It's chiefly used for scrolling and the bar indicating its
 # position. Letting the estimate be too inaccurate results in a display bug, so
@@ -139,11 +137,6 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
     self._last_logged_events = []
 
-    # _get_title (args: logged_events, regex_filter pattern, width)
-
-    self._title_cache = None
-    self._title_args = (None, None, None)
-
     self.reprepopulate_events()
 
     # leaving last_content_height as being too low causes initialization problems
@@ -531,7 +524,15 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       # draws the top label
 
       if self.is_title_visible():
-        self.addstr(0, 0, self._get_title(width), curses.A_STANDOUT)
+        comp = condense_runlevels(*self.logged_events)
+
+        if self.regex_filter:
+          comp.append('filter: %s' % self.regex_filter)
+
+        comp_str = join(comp, ', ', width - 10)
+        title = 'Events (%s):' % comp_str if comp_str else 'Events:'
+
+        self.addstr(0, 0, title, curses.A_STANDOUT)
 
       # restricts scroll location to valid bounds
 
@@ -618,7 +619,7 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
         if duplicate_count:
           plural_label = 's' if duplicate_count > 1 else ''
-          duplicate_msg = DUPLICATE_MSG % (duplicate_count, plural_label)
+          duplicate_msg = ' [%i duplicate%s hidden]' % (duplicate_count, plural_label)
           display_queue.append((duplicate_msg, duplicate_attr, False))
 
         # TODO: a fix made line_offset unused, and probably broke max_entries_per_line... not sure if we care
@@ -758,16 +759,8 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
     # accounts for runlevel naming difference
 
-    if 'ERROR' in events:
-      events.add('ERR')
-      events.remove('ERROR')
-
-    if 'WARNING' in events:
-      events.add('WARN')
-      events.remove('WARNING')
-
     tor_events = events.intersection(set(nyx.arguments.TOR_EVENT_TYPES.values()))
-    nyx_events = events.intersection(set(['NYX_%s' % runlevel for runlevel in log.Runlevel.keys()]))
+    nyx_events = events.intersection(set(['NYX_%s' % runlevel for runlevel in TOR_RUNLEVELS]))
 
     # adds events unrecognized by nyx if we're listening to the 'UNKNOWN' type
 
@@ -796,122 +789,3 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       self.redraw(True)
     elif event_type == State.CLOSED:
       log.notice('Tor control port closed')
-
-  def _get_title(self, width):
-    """
-    Provides the label used for the panel, looking like:
-      Events (NYX NOTICE - ERR, BW - filter: prepopulate):
-
-    This truncates the attributes (with an ellipse) if too long, and condenses
-    runlevel ranges if there's three or more in a row (for instance NYX_INFO,
-    NYX_NOTICE, and NYX_WARN becomes 'NYX_INFO - WARN').
-
-    Arguments:
-      width - width constraint the label needs to fix in
-    """
-
-    # usually the attributes used to make the label are decently static, so
-    # provide cached results if they're unchanged
-
-    with self.vals_lock:
-      current_pattern = self.regex_filter.pattern if self.regex_filter else None
-      is_unchanged = self._title_args[0] == self.logged_events
-      is_unchanged &= self._title_args[1] == current_pattern
-      is_unchanged &= self._title_args[2] == width
-
-      if is_unchanged:
-        return self._title_cache
-
-      events_list = list(self.logged_events)
-
-      if not events_list:
-        if not current_pattern:
-          panel_label = 'Events:'
-        else:
-          label_pattern = str_tools.crop(current_pattern, width - 18)
-          panel_label = 'Events (filter: %s):' % label_pattern
-      else:
-        # does the following with all runlevel types (tor, nyx, and stem):
-        # - pulls to the start of the list
-        # - condenses range if there's three or more in a row (ex. "NYX_INFO - WARN")
-        # - condense further if there's identical runlevel ranges for multiple
-        #   types (ex. "NOTICE - ERR, NYX_NOTICE - ERR" becomes "TOR/NYX NOTICE - ERR")
-
-        tmp_runlevels = []  # runlevels pulled from the list (just the runlevel part)
-        runlevel_ranges = []  # tuple of type, start_level, end_level for ranges to be consensed
-
-        # reverses runlevels and types so they're appended in the right order
-
-        reversed_runlevels = list(log.Runlevel)
-        reversed_runlevels.reverse()
-
-        for prefix in ('NYX_', ''):
-          # blank ending runlevel forces the break condition to be reached at the end
-          for runlevel in reversed_runlevels + ['']:
-            event_type = prefix + runlevel
-            if runlevel and event_type in events_list:
-              # runlevel event found, move to the tmp list
-              events_list.remove(event_type)
-              tmp_runlevels.append(runlevel)
-            elif tmp_runlevels:
-              # adds all tmp list entries to the start of events_list
-              if len(tmp_runlevels) >= 3:
-                # save condense sequential runlevels to be added later
-                runlevel_ranges.append((prefix, tmp_runlevels[-1], tmp_runlevels[0]))
-              else:
-                # adds runlevels individaully
-                for tmp_runlevel in tmp_runlevels:
-                  events_list.insert(0, prefix + tmp_runlevel)
-
-              tmp_runlevels = []
-
-        # adds runlevel ranges, condensing if there's identical ranges
-
-        for i in range(len(runlevel_ranges)):
-          if runlevel_ranges[i]:
-            prefix, start_level, end_level = runlevel_ranges[i]
-
-            # check for matching ranges
-
-            matches = []
-
-            for j in range(i + 1, len(runlevel_ranges)):
-              if runlevel_ranges[j] and runlevel_ranges[j][1] == start_level and runlevel_ranges[j][2] == end_level:
-                matches.append(runlevel_ranges[j])
-                runlevel_ranges[j] = None
-
-            if matches:
-              # strips underscores and replaces empty entries with "TOR"
-
-              prefixes = [entry[0] for entry in matches] + [prefix]
-
-              for k in range(len(prefixes)):
-                if prefixes[k] == '':
-                  prefixes[k] = 'TOR'
-                else:
-                  prefixes[k] = prefixes[k].replace('_', '')
-
-              events_list.insert(0, '%s %s - %s' % ('/'.join(prefixes), start_level, end_level))
-            else:
-              events_list.insert(0, '%s%s - %s' % (prefix, start_level, end_level))
-
-        # truncates to use an ellipsis if too long, for instance:
-
-        attr_label = ', '.join(events_list)
-
-        if current_pattern:
-          attr_label += ' - filter: %s' % current_pattern
-
-        attr_label = str_tools.crop(attr_label, width - 10, 1)
-
-        if attr_label:
-          attr_label = ' (%s)' % attr_label
-
-        panel_label = 'Events%s:' % attr_label
-
-      # cache results and return
-
-      self._title_cache = panel_label
-      self._title_args = (list(self.logged_events), current_pattern, width)
-
-      return panel_label
diff --git a/nyx/util/log.py b/nyx/util/log.py
index 58126a7..42c7ed5 100644
--- a/nyx/util/log.py
+++ b/nyx/util/log.py
@@ -35,6 +35,75 @@ def days_since(timestamp):
 
 
 @lru_cache()
+def condense_runlevels(*events):
+  """
+  Provides runlevel events with condensed. For example...
+
+    >>> condense_runlevels(['DEBUG', 'NOTICE', 'WARN', 'ERR', 'NYX_NOTICE', 'NYX_WARN', 'NYX_ERR', 'BW'])
+    ['TOR/NYX NOTICE-ERROR', 'DEBUG', 'BW']
+
+  :param list events: event types to be condensed
+
+  :returns: **list** of the input events, with condensed runlevels
+  """
+
+  def ranges(runlevels):
+    ranges = []
+
+    while runlevels:
+      # provides the (start, end) for a contiguous range
+      start = end = runlevels[0]
+
+      for r in TOR_RUNLEVELS[TOR_RUNLEVELS.index(start):]:
+        if r in runlevels:
+          runlevels.remove(r)
+          end = r
+        else:
+          break
+
+      ranges.append((start, end))
+
+    return ranges
+
+  events = list(events)
+  tor_runlevels, nyx_runlevels = [], []
+
+  for r in TOR_RUNLEVELS:
+    if r in events:
+      tor_runlevels.append(r)
+      events.remove(r)
+
+    if 'NYX_%s' % r in events:
+      nyx_runlevels.append(r)
+      events.remove('NYX_%s' % r)
+
+  tor_ranges = ranges(tor_runlevels)
+  nyx_ranges = ranges(nyx_runlevels)
+
+  result = []
+
+  for runlevel_range in tor_ranges:
+    if runlevel_range[0] == runlevel_range[1]:
+      range_label = runlevel_range[0]
+    else:
+      range_label = '%s-%s' % (runlevel_range[0], runlevel_range[1])
+
+    if runlevel_range in nyx_ranges:
+      result.append('TOR/NYX %s' % range_label)
+      nyx_ranges.remove(runlevel_range)
+    else:
+      result.append(range_label)
+
+  for runlevel_range in nyx_ranges:
+    if runlevel_range[0] == runlevel_range[1]:
+      result.append('NYX %s' % runlevel_range[0])
+    else:
+      result.append('NYX %s-%s' % (runlevel_range[0], runlevel_range[1]))
+
+  return result + events
+
+
+ at lru_cache()
 def _common_log_messages():
   """
   Provides a mapping of message types to its common log messages. These are
diff --git a/test/util/log/condense_runlevels.py b/test/util/log/condense_runlevels.py
new file mode 100644
index 0000000..d9e62cd
--- /dev/null
+++ b/test/util/log/condense_runlevels.py
@@ -0,0 +1,13 @@
+import unittest
+
+from nyx.util.log import condense_runlevels
+
+
+class TestCondenseRunlevels(unittest.TestCase):
+  def test_condense_runlevels(self):
+    self.assertEqual([], condense_runlevels())
+    self.assertEqual(['BW'], condense_runlevels('BW'))
+    self.assertEqual(['DEBUG', 'NOTICE', 'ERR'], condense_runlevels('DEBUG', 'NOTICE', 'ERR'))
+    self.assertEqual(['DEBUG-NOTICE', 'NYX DEBUG-INFO'], condense_runlevels('DEBUG', 'NYX_DEBUG', 'INFO', 'NYX_INFO', 'NOTICE'))
+    self.assertEqual(['TOR/NYX NOTICE-ERR'], condense_runlevels('NOTICE', 'WARN', 'ERR', 'NYX_NOTICE', 'NYX_WARN', 'NYX_ERR'))
+    self.assertEqual(['DEBUG', 'TOR/NYX NOTICE-ERR', 'BW'], condense_runlevels('DEBUG', 'NOTICE', 'WARN', 'ERR', 'NYX_NOTICE', 'NYX_WARN', 'NYX_ERR', 'BW'))





More information about the tor-commits mailing list