[tor-commits] [stem/master] Lazy fetching for help responses

atagar at torproject.org atagar at torproject.org
Tue May 6 01:21:13 UTC 2014


commit 267ea3a7815ed39a8360c088b7ff6bdd20e79b84
Author: Damian Johnson <atagar at torproject.org>
Date:   Sat May 3 12:51:04 2014 -0700

    Lazy fetching for help responses
    
    Our prior commit changed /help to be eagerly feched and chached. Caching for
    this is good, but cached lazy fetches are even better. No reason to make a
    handful of GETINFO requests at startup if they won't be needed.
---
 stem/interpretor/commands.py |  172 ++++++++++++++++++++++--------------------
 1 file changed, 90 insertions(+), 82 deletions(-)

diff --git a/stem/interpretor/commands.py b/stem/interpretor/commands.py
index f43e0d1..a6d1894 100644
--- a/stem/interpretor/commands.py
+++ b/stem/interpretor/commands.py
@@ -15,100 +15,127 @@ OUTPUT_FORMAT = (Color.BLUE, )
 BOLD_OUTPUT_FORMAT = (Color.BLUE, Attr.BOLD)
 ERROR_FORMAT = (Attr.BOLD, Color.RED)
 
+try:
+  # added in python 3.2
+  from functools import lru_cache
+except ImportError:
+  from stem.util.lru_cache import lru_cache
+
 
 @uses_settings
-def help_output(controller, config):
+def help_output(controller, arg, config):
   """
-  Provides the output for our /help commands.
+  Provides our /help response.
 
   :param stem.Controller controller: tor control connection
+  :param str arg: controller or interpretor command to provide help output for
   :param stem.util.conf.Config config: interpretor configuration
 
-  :returns: **dict** mapping arguments to their help output
+  :returns: **str** with our help response
   """
 
-  result = {}
-  usage_info = config.get('help.usage', {})
+  # Normalizing inputs first so we can better cache responses.
 
-  general_help = ''
+  arg = arg.upper()
 
-  for line in msg('help.general').splitlines():
-    cmd_start = line.find(' - ')
+  # If there's multiple arguments then just take the first. This is
+  # particularly likely if they're trying to query a full command (for
+  # instance "/help GETINFO version")
 
-    if cmd_start != -1:
-      general_help += format(line[:cmd_start], *BOLD_OUTPUT_FORMAT)
-      general_help += format(line[cmd_start:] + '\n', *OUTPUT_FORMAT)
-    else:
-      general_help += format(line + '\n', *BOLD_OUTPUT_FORMAT)
+  arg = arg.split(' ')[0]
+
+  # strip slash if someone enters an interpretor command (ex. "/help /help")
+
+  if arg.startswith('/'):
+    arg = arg[1:]
 
-  result[''] = general_help
+  return _help_output(controller, arg, config)
 
-  for arg, usage in usage_info.items():
-    description = config.get('help.description.%s' % arg.lower(), '')
 
-    output = format(usage + '\n', *BOLD_OUTPUT_FORMAT)
+ at lru_cache()
+def _help_output(controller, arg, config):
+  if not arg:
+    general_help = ''
 
-    for line in description.splitlines():
-      output += format('  ' + line + '\n', *OUTPUT_FORMAT)
+    for line in msg('help.general').splitlines():
+      cmd_start = line.find(' - ')
+
+      if cmd_start != -1:
+        general_help += format(line[:cmd_start], *BOLD_OUTPUT_FORMAT)
+        general_help += format(line[cmd_start:] + '\n', *OUTPUT_FORMAT)
+      else:
+        general_help += format(line + '\n', *BOLD_OUTPUT_FORMAT)
+
+    return general_help
+
+  usage_info = config.get('help.usage', {})
 
-    output += '\n'
+  if not arg in usage_info:
+    return format("No help information available for '%s'..." % arg, *ERROR_FORMAT)
 
-    if arg == 'GETINFO':
-      results = controller.get_info('info/names', None)
+  output = format(usage_info[arg] + '\n', *BOLD_OUTPUT_FORMAT)
 
-      if results:
-        for line in results.splitlines():
-          if ' -- ' in line:
-            opt, summary = line.split(' -- ', 1)
+  description = config.get('help.description.%s' % arg.lower(), '')
 
-            output += format("%-33s" % opt, *BOLD_OUTPUT_FORMAT)
-            output += format(" - %s\n" % summary, *OUTPUT_FORMAT)
-    elif arg == 'GETCONF':
-      results = controller.get_info('config/names', None)
+  for line in description.splitlines():
+    output += format('  ' + line + '\n', *OUTPUT_FORMAT)
 
-      if results:
-        options = [opt.split(' ', 1)[0] for opt in results.splitlines()]
+  output += '\n'
 
-        for i in range(0, len(options), 2):
-          line = ''
+  if arg == 'GETINFO':
+    results = controller.get_info('info/names', None)
 
-          for entry in options[i:i + 1]:
-            line += '%-42s' % entry
+    if results:
+      for line in results.splitlines():
+        if ' -- ' in line:
+          opt, summary = line.split(' -- ', 1)
 
-          output += format(line + '\n', *OUTPUT_FORMAT)
-    elif arg == 'SIGNAL':
-      signal_options = config.get('help.signal.options', {})
+          output += format("%-33s" % opt, *BOLD_OUTPUT_FORMAT)
+          output += format(" - %s\n" % summary, *OUTPUT_FORMAT)
+  elif arg == 'GETCONF':
+    results = controller.get_info('config/names', None)
 
-      for signal, summary in signal_options.items():
-        output += format('%-15s' % signal, *BOLD_OUTPUT_FORMAT)
-        output += format(' - %s\n' % summary, *OUTPUT_FORMAT)
-    elif arg == 'SETEVENTS':
-      results = controller.get_info('events/names', None)
+    if results:
+      options = [opt.split(' ', 1)[0] for opt in results.splitlines()]
 
-      if results:
-        entries = results.split()
+      for i in range(0, len(options), 2):
+        line = ''
 
-        # displays four columns of 20 characters
+        for entry in options[i:i + 1]:
+          line += '%-42s' % entry
 
-        for i in range(0, len(entries), 4):
-          line = ''
+        output += format(line + '\n', *OUTPUT_FORMAT)
+  elif arg == 'SIGNAL':
+    signal_options = config.get('help.signal.options', {})
 
-          for entry in entries[i:i + 4]:
-            line += '%-20s' % entry
+    for signal, summary in signal_options.items():
+      output += format('%-15s' % signal, *BOLD_OUTPUT_FORMAT)
+      output += format(' - %s\n' % summary, *OUTPUT_FORMAT)
+  elif arg == 'SETEVENTS':
+    results = controller.get_info('events/names', None)
 
-          output += format(line + '\n', *OUTPUT_FORMAT)
-    elif arg == 'USEFEATURE':
-      results = controller.get_info('features/names', None)
+    if results:
+      entries = results.split()
 
-      if results:
-        output += format(results + '\n', *OUTPUT_FORMAT)
-    elif arg in ('LOADCONF', 'POSTDESCRIPTOR'):
-      # gives a warning that this option isn't yet implemented
-      output += format(msg('msg.multiline_unimplemented_notice') + '\n', *ERROR_FORMAT)
+      # displays four columns of 20 characters
 
-    result[arg] = output
+      for i in range(0, len(entries), 4):
+        line = ''
 
-  return result
+        for entry in entries[i:i + 4]:
+          line += '%-20s' % entry
+
+        output += format(line + '\n', *OUTPUT_FORMAT)
+  elif arg == 'USEFEATURE':
+    results = controller.get_info('features/names', None)
+
+    if results:
+      output += format(results + '\n', *OUTPUT_FORMAT)
+  elif arg in ('LOADCONF', 'POSTDESCRIPTOR'):
+    # gives a warning that this option isn't yet implemented
+    output += format(msg('msg.multiline_unimplemented_notice') + '\n', *ERROR_FORMAT)
+
+  return output
 
 
 class ControlInterpretor(object):
@@ -120,7 +147,6 @@ class ControlInterpretor(object):
   def __init__(self, controller):
     self._controller = controller
     self._received_events = []
-    self._help_output = help_output(controller)
 
   def register_event(self, event):
     """
@@ -129,31 +155,13 @@ class ControlInterpretor(object):
 
     self._received_events.append(event)
 
-  @uses_settings
-  def do_help(self, arg, config):
+  def do_help(self, arg):
     """
     Performs the '/help' operation, giving usage information for the given
     argument or a general summary if there wasn't one.
     """
 
-    arg = arg.upper()
-    usage_info = config.get('help.usage', {})
-
-    # If there's multiple arguments then just take the first. This is
-    # particularly likely if they're trying to query a full command (for
-    # instance "/help GETINFO version")
-
-    arg = arg.split(' ')[0]
-
-    # strip slash if someone enters an interpretor command (ex. "/help /help")
-
-    if arg.startswith('/'):
-      arg = arg[1:]
-
-    if arg in self._help_output:
-      return self._help_output[arg]
-    else:
-      return format("No help information available for '%s'..." % arg, *ERROR_FORMAT)
+    return help_output(self._controller, arg)
 
   def do_events(self, arg):
     """





More information about the tor-commits mailing list