# Copyright 2014-2018, Damian Johnson and The Tor Project # See LICENSE for licensing information """ Provides our /help responses. """ import stem.prereq from stem.interpreter import ( STANDARD_OUTPUT, BOLD_OUTPUT, ERROR_OUTPUT, msg, uses_settings, ) from stem.util.term import format if stem.prereq._is_lru_cache_available(): from functools import lru_cache else: from stem.util.lru_cache import lru_cache def response(controller, arg): """ Provides our /help response. :param stem.control.Controller controller: tor control connection :param str arg: controller or interpreter command to provide help output for :returns: **str** with our help response """ # Normalizing inputs first so we can better cache responses. return _response(controller, _normalize(arg)) def _normalize(arg): arg = arg.upper() # 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 interpreter command (ex. "/help /help") if arg.startswith('/'): arg = arg[1:] return arg @lru_cache() @uses_settings def _response(controller, arg, config): if not arg: return _general_help() usage_info = config.get('help.usage', {}) if arg not in usage_info: return format("No help information available for '%s'..." % arg, *ERROR_OUTPUT) output = format(usage_info[arg] + '\n', *BOLD_OUTPUT) description = config.get('help.description.%s' % arg.lower(), '') for line in description.splitlines(): output += format(' ' + line, *STANDARD_OUTPUT) + '\n' output += '\n' if arg == 'GETINFO': results = controller.get_info('info/names', None) if results: for line in results.splitlines(): if ' -- ' in line: opt, summary = line.split(' -- ', 1) output += format('%-33s' % opt, *BOLD_OUTPUT) output += format(' - %s' % summary, *STANDARD_OUTPUT) + '\n' elif arg == 'GETCONF': results = controller.get_info('config/names', None) if results: options = [opt.split(' ', 1)[0] for opt in results.splitlines()] for i in range(0, len(options), 2): line = '' for entry in options[i:i + 2]: line += '%-42s' % entry output += format(line.rstrip(), *STANDARD_OUTPUT) + '\n' elif arg == 'SIGNAL': signal_options = config.get('help.signal.options', {}) for signal, summary in signal_options.items(): output += format('%-15s' % signal, *BOLD_OUTPUT) output += format(' - %s' % summary, *STANDARD_OUTPUT) + '\n' elif arg == 'SETEVENTS': results = controller.get_info('events/names', None) if results: entries = results.split() # displays four columns of 20 characters for i in range(0, len(entries), 4): line = '' for entry in entries[i:i + 4]: line += '%-20s' % entry output += format(line.rstrip(), *STANDARD_OUTPUT) + '\n' elif arg == 'USEFEATURE': results = controller.get_info('features/names', None) if results: output += format(results, *STANDARD_OUTPUT) + '\n' elif arg in ('LOADCONF', 'POSTDESCRIPTOR'): # gives a warning that this option isn't yet implemented output += format(msg('msg.multiline_unimplemented_notice'), *ERROR_OUTPUT) + '\n' return output.rstrip() def _general_help(): lines = [] for line in msg('help.general').splitlines(): div = line.find(' - ') if div != -1: cmd, description = line[:div], line[div:] lines.append(format(cmd, *BOLD_OUTPUT) + format(description, *STANDARD_OUTPUT)) else: lines.append(format(line, *BOLD_OUTPUT)) return '\n'.join(lines)