commit 267ea3a7815ed39a8360c088b7ff6bdd20e79b84 Author: Damian Johnson atagar@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) +@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): """
tor-commits@lists.torproject.org