1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
# Copyright 2014-2020, Damian Johnson and The Tor Project
# See LICENSE for licensing information
"""
Provides our /help responses.
"""
import functools
from typing import cast
import stem.control
import stem.util.conf
from stem.util.term import format
from stem.interpreter import (
STANDARD_OUTPUT,
BOLD_OUTPUT,
ERROR_OUTPUT,
msg,
uses_settings,
)
def response(controller: stem.control.Controller, arg: str) -> str:
"""
Provides our /help response.
:param controller: tor control connection
:param 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: str) -> str:
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
@functools.lru_cache()
@uses_settings
def _response(controller: stem.control.Controller, arg: str, config: stem.util.conf.Config) -> str:
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 = cast(str, 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 = cast(str, 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 = cast(str, 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 = cast(str, 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() -> str:
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)
|