-
Notifications
You must be signed in to change notification settings - Fork 0
/
shell_help_mixin.py
140 lines (122 loc) · 5.37 KB
/
shell_help_mixin.py
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
"""Mixin class that adds online help to a friendly shell"""
import inspect
import tabulate
class ShellHelpMixin(object):
"""Mixin class to be added to any friendly shell to add online help"""
def __init__(self, *args, **kwargs): # pylint: disable=useless-super-delegation
super(ShellHelpMixin, self).__init__(*args, **kwargs)
def _list_commands(self):
"""Displays a list of supported commands"""
all_methods = inspect.getmembers(self, inspect.ismethod)
command_list = {
'Command': [],
'Description': [],
'Extended Help': [],
}
for cur_method in all_methods:
# Each method definition is a 2-tuple, with the first element being
# the name of the method and the second a reference to the method
# object
method_name = cur_method[0]
method_obj = cur_method[1]
self.debug('Checking for command method %s', method_name)
# Methods that start with 'do_' are interpreted as command operator
if method_name.startswith('do_'):
self.debug("Found a do command %s", method_name)
# Extrapolate the command name by removing the 'do_' prefix
cmd_name = cur_method[0][3:]
# Generate our help data
command_list['Command'].append(cmd_name)
doc_string = inspect.getdoc(method_obj) or ''
command_list['Description'].append(doc_string.split('\n')[0])
if hasattr(self, method_name.replace('do_', 'help_')):
self.debug(
"Found an associated help method %s",
method_name.replace('do_', 'help_')
)
# NOTE:
# For the sake of online help, we'll assume that class
# attributes with 'help_' in their name are methods which
# can be called to display verbose help. We can add
# verification logic for this elsewhere when necessary
command_list['Extended Help'].append(
'`' + self.prompt + 'help ' + cmd_name + '`')
else:
command_list['Extended Help'].append('N/A')
self.info("COMMANDS")
self.info(tabulate.tabulate(command_list, headers="keys"))
def _list_operators(self):
"""Displays a list of built-in operators supported by Friendly Shell"""
operator_list = {
'Operator': ["!"],
'Description': [
"Redirects command to the native console"
],
'Examples': [
"'!dir /ah', '!ls -alh'"
]
}
self.info("OPERATORS")
self.info(tabulate.tabulate(operator_list, headers="keys"))
def do_help(self, arg=None):
"""Online help generation (this command)
:param str arg:
Optional command to generate online help for.
If not defined, show a list of commands
"""
# no command given, show available commands
if arg is None:
self.debug("Showing default help output...")
self._list_commands()
self.info("\n")
self._list_operators()
return
# Sanity check: make sure we're asking for help for a command
# that actually exists
cmd_method_name = 'do_' + arg
if not hasattr(self, cmd_method_name):
self.error("Command does not exist: %s", arg)
return
cmd_method = getattr(self, cmd_method_name)
if not inspect.ismethod(cmd_method):
self.error(
'Error: definition "%s" in derived class must be a method. '
'Check implementation',
cmd_method_name)
return
# Next, see if there's a "help_<cmd>" method on the class
method_name = 'help_' + arg
if hasattr(self, method_name):
func = getattr(self, method_name)
if not inspect.ismethod(func):
self.error(
'Error: definition "%s" in derived class must be a method. '
'Check implementation',
method_name)
return
self.info(func())
return
# If no explicit help method, parse the help from the doc string for
# the command method
docs = cmd_method.__doc__
if docs:
self.info(docs.split('\n')[0])
return
# If we get here then there's no online help defined for the command
# anywhere in the class hierarchy
self.info('No online help for command "%s"', arg)
def complete_help(self, parser, parameter_index, cursor_position):
"""Automatic completion method for the 'help' command"""
return self._complete_command_names(
parser[parameter_index][:cursor_position])
def help_help(self):
"""Generates inline help for the 'help' command"""
retval = [
"Online help generation tool",
"Running 'help' with no parameters displays a list of supported "
"commands",
"Passing any supported command to 'help' provides detailed help on "
"the command",
"example: " + self.prompt + "help exit"
]
return '\n'.join(retval)