forked from P1sec/QCSuper
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcli.py
186 lines (114 loc) · 5.94 KB
/
cli.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/python3
#-*- encoding: Utf-8 -*-
from os.path import isdir, expanduser
from traceback import print_exc
from sys import argv, stdout
from subprocess import run
from shutil import which
from shlex import split
from glob import glob
from re import sub
"""
This module allows the user to use a command prompt, to which it will be
able to send arguments that directly map to the standard arguments of the
program. For example, "./qcsuper.py --pcap-dump test.pcap" will map to the
command "pcap-dump test.pcap".
Due to the blocking behavior of this task, it is run in a separate thread.
"""
class CommandLineInterface:
"""
:param diag_input: The object for the input mode chosen by the user.
:param parser: The original ArgumentParser for the program.
:param parse_modules_args: A callback receiving parsed again arguments,
where the original argv has been concatenated with the module the
user has just queried through the CLI.
"""
def __init__(self, diag_input, parser, parse_modules_args):
self.diag_input = diag_input
self.parser = parser
self.parse_modules_args = parse_modules_args
self.parser.print_help = self.print_help
"""
Process commands coming from stdout.
"""
def on_init(self):
print()
print('Welcome to the QCSuper CLI. Type "help" for a list of available commands.')
print()
command_to_module = {} # {"raw user-issued command": Module()}
self.setup_readline()
while True:
try:
line = input('>>> ')
if line.strip().lower() in ('q', 'quit', 'exit'):
raise EOFError
except (EOFError, IOError):
# Interrupt the main and the current thread
with self.diag_input.shutdown_event:
self.diag_input.shutdown_event.notify()
return
if line.strip().startswith('stop '):
# Stop a running command
command = line.replace('stop', '', 1).strip()
if command_to_module.get(command, None) in self.diag_input.modules:
self.diag_input.remove_module(command_to_module[command])
print('Command stopped')
else:
print('Command "%s" does not appear to be running' % command)
elif line:
# Launch a new command
try:
parsed_again_args = self.parser.parse_args(argv[1:] + split('--' + line.strip('- \t')))
old_number_of_modules = len(self.diag_input.modules)
self.parse_modules_args(parsed_again_args)
if len(self.diag_input.modules) == old_number_of_modules + 1:
command_to_module[line.strip()] = self.diag_input.modules[-1]
print('Command started in the background, you can stop it using "stop %s"' % line.strip())
except SystemExit:
pass
except Exception:
print_exc()
"""
Enable using the direction keys and autocompletion for the command line.
"""
def setup_readline(self):
try:
from readline import parse_and_bind, set_completer, set_completer_delims
except ImportError:
pass
else:
def complete_command_or_path(text, nb_tries):
try:
# Match commands
matches = []
for command_prefix in ['-', '']:
matches += [
arg.strip(command_prefix) + ' ' for arg in self.parser._option_string_actions
if arg.strip(command_prefix).startswith(text.strip(command_prefix))
]
# Match directories and files
matches += [
path + '/' if isdir(path) else path + ' '
for path in glob(expanduser(text + '*'))
]
return matches[nb_tries] if nb_tries < len(matches) else None
except Exception:
print_exc()
set_completer(complete_command_or_path)
set_completer_delims(' \t\n')
parse_and_bind('tab: complete')
"""
Print the help for the command-line prompt, adapting the original
output from ArgumentParser.
"""
def print_help(self):
help_text = self.parser.format_help()
_, help_modules_prefix, help_modules = help_text.partition('Modules:')
help_modules, help_options_prefix, help_options = help_modules.partition('options:')
print(
'\nCommand format: module_name [ARGUMENT] [--option [ARGUMENT]]\n\n' +
help_modules_prefix + sub('--', '', help_modules) +
help_options_prefix + sub('--([\w-]+-dump)', r'"\1"', help_options)
)
def on_deinit(self):
print('')