commit 278994377919bef35dcf6fef604d7bf5ef3be6da Author: Damian Johnson atagar@torproject.org Date: Sat Apr 19 16:23:01 2014 -0700
Moving autocompletion to its own module
I bundled autocompletion with commands.py because it used one of its globals. However, we've since dropped those bits so this can be nicely moved back to its own file. --- stem/interpretor/__init__.py | 7 +-- stem/interpretor/autocomplete.py | 98 ++++++++++++++++++++++++++++++++++++++ stem/interpretor/commands.py | 93 ------------------------------------ 3 files changed, 102 insertions(+), 96 deletions(-)
diff --git a/stem/interpretor/__init__.py b/stem/interpretor/__init__.py index 59c89c8..5e03c2c 100644 --- a/stem/interpretor/__init__.py +++ b/stem/interpretor/__init__.py @@ -6,7 +6,7 @@ Interactive interpretor for interacting with Tor directly. This adds usability features such as tab completion, history, and IRC-style functions (like /help). """
-__all__ = ['arguments', 'commands', 'msg'] +__all__ = ['arguments', 'autocomplete', 'commands', 'msg']
import os import sys @@ -39,6 +39,7 @@ def main(): import readline
import stem.interpretor.arguments + import stem.interpretor.autocomplete import stem.interpretor.commands
try: @@ -64,9 +65,9 @@ def main(): sys.exit(1)
with controller: - autocomplete = stem.interpretor.commands.Autocomplete(controller) + autocompleter = stem.interpretor.autocomplete.Autocompleter(controller) readline.parse_and_bind('tab: complete') - readline.set_completer(autocomplete.complete) + readline.set_completer(autocompleter.complete) readline.set_completer_delims('\n')
interpretor = stem.interpretor.commands.ControlInterpretor(controller) diff --git a/stem/interpretor/autocomplete.py b/stem/interpretor/autocomplete.py new file mode 100644 index 0000000..09c5365 --- /dev/null +++ b/stem/interpretor/autocomplete.py @@ -0,0 +1,98 @@ +""" +Tab completion for our interpretor prompt. +""" + +from stem.interpretor import uses_settings + + +@uses_settings +def _get_commands(config, controller): + """ + Provides commands recognized by tor. + """ + + commands = config.get('autocomplete', []) + + # GETINFO commands + + getinfo_options = controller.get_info('info/names', None) + + if getinfo_options: + # Lines are of the form '[option] -- [description]'. This strips '*' from + # options that accept values. + + options = [line.split(' ', 1)[0].rstrip('*') for line in getinfo_options.splitlines()] + + commands += ['GETINFO %s' % opt for opt in options] + else: + commands.append('GETINFO ') + + # GETCONF, SETCONF, and RESETCONF commands + + config_options = controller.get_info('config/names', None) + + if config_options: + # individual options are '[option] [type]' pairs + + entries = [opt.split(' ', 1)[0] for opt in config_options.splitlines()] + + commands += ['GETCONF %s' % opt for opt in entries] + commands += ['SETCONF %s ' % opt for opt in entries] + commands += ['RESETCONF %s' % opt for opt in entries] + else: + commands += ['GETCONF ', 'SETCONF ', 'RESETCONF '] + + # SETEVENT commands + + events = controller.get_info('events/names', None) + + if events: + commands += ['SETEVENTS %s' % event for event in events.split(' ')] + else: + commands.append('SETEVENTS ') + + # USEFEATURE commands + + features = controller.get_info('features/names', None) + + if features: + commands += ['USEFEATURE %s' % feature for feature in features.split(' ')] + else: + commands.append('USEFEATURE ') + + # SIGNAL commands + + signals = controller.get_info('signal/names', None) + + if signals: + commands += ['SIGNAL %s' % signal for signal in signals.split(' ')] + else: + commands.append('SIGNAL ') + + # adds help options for the previous commands + + base_cmd = set([cmd.split(' ')[0].replace('+', '').replace('/', '') for cmd in commands]) + + for cmd in base_cmd: + commands.append('/help ' + cmd) + + return commands + + +class Autocompleter(object): + def __init__(self, controller): + self._commands = _get_commands(controller) + + def complete(self, text, state): + """ + Provides case insensetive autocompletion options, acting as a functor for + the readlines set_completer function. + """ + + lowercase_text = text.lower() + prefix_matches = [cmd for cmd in self._commands if cmd.lower().startswith(lowercase_text)] + + if state < len(prefix_matches): + return prefix_matches[state] + else: + return None diff --git a/stem/interpretor/commands.py b/stem/interpretor/commands.py index 833918f..e5b3e97 100644 --- a/stem/interpretor/commands.py +++ b/stem/interpretor/commands.py @@ -16,99 +16,6 @@ BOLD_OUTPUT_FORMAT = (Color.BLUE, Attr.BOLD) ERROR_FORMAT = (Attr.BOLD, Color.RED)
-@uses_settings -def _get_commands(config, controller): - """ - Provides commands recognized by tor. - """ - - commands = config.get('autocomplete', []) - - # GETINFO commands - - getinfo_options = controller.get_info('info/names', None) - - if getinfo_options: - # Lines are of the form '[option] -- [description]'. This strips '*' from - # options that accept values. - - options = [line.split(' ', 1)[0].rstrip('*') for line in getinfo_options.splitlines()] - - commands += ['GETINFO %s' % opt for opt in options] - else: - commands.append('GETINFO ') - - # GETCONF, SETCONF, and RESETCONF commands - - config_options = controller.get_info('config/names', None) - - if config_options: - # individual options are '[option] [type]' pairs - - entries = [opt.split(' ', 1)[0] for opt in config_options.splitlines()] - - commands += ['GETCONF %s' % opt for opt in entries] - commands += ['SETCONF %s ' % opt for opt in entries] - commands += ['RESETCONF %s' % opt for opt in entries] - else: - commands += ['GETCONF ', 'SETCONF ', 'RESETCONF '] - - # SETEVENT commands - - events = controller.get_info('events/names', None) - - if events: - commands += ['SETEVENTS %s' % event for event in events.split(' ')] - else: - commands.append('SETEVENTS ') - - # USEFEATURE commands - - features = controller.get_info('features/names', None) - - if features: - commands += ['USEFEATURE %s' % feature for feature in features.split(' ')] - else: - commands.append('USEFEATURE ') - - # SIGNAL commands - - signals = controller.get_info('signal/names', None) - - if signals: - commands += ['SIGNAL %s' % signal for signal in signals.split(' ')] - else: - commands.append('SIGNAL ') - - # adds help options for the previous commands - - base_cmd = set([cmd.split(' ')[0].replace('+', '').replace('/', '') for cmd in commands]) - - for cmd in base_cmd: - commands.append('/help ' + cmd) - - return commands - - -class Autocomplete(object): - def __init__(self, controller): - self._commands = _get_commands(controller) - - def complete(self, text, state): - """ - Provides case insensetive autocompletion options, acting as a functor for - the readlines set_completer function. - """ - - lowercase_text = text.lower() - prefix_matches = [cmd for cmd in self._commands if cmd.lower().startswith(lowercase_text)] - - if state < len(prefix_matches): - return prefix_matches[state] - else: - return None - - class ControlInterpretor(object): """ Handles issuing requests and providing nicely formed responses, with support
tor-commits@lists.torproject.org