diff options
author | Lars Amsel <lars.amsel@ni.com> | 2019-11-19 14:43:33 +0100 |
---|---|---|
committer | atrnati <54334261+atrnati@users.noreply.github.com> | 2020-02-07 09:14:41 -0600 |
commit | d1458a2cf3a735c21cc61080b21f0b46eea22d44 (patch) | |
tree | 17c70ffced8fd34dc8d3bb7317ac38fe715d81e7 | |
parent | 965ad0527935f37d99f5f3f28dd27328e6af1ef8 (diff) | |
download | uhd-d1458a2cf3a735c21cc61080b21f0b46eea22d44.tar.gz uhd-d1458a2cf3a735c21cc61080b21f0b46eea22d44.tar.bz2 uhd-d1458a2cf3a735c21cc61080b21f0b46eea22d44.zip |
MPM: add ability to run scripts to MPM shell
MPM shell now supports script execution. It utilizes the cmdqueue of
Pythons cmd.Cmd class for this. The script to execute is a text file
containing the commands one per line.
The output decoration of MPM shell changed. Commands are decorated with
">" whereas responses use "<" at line start. Multiline responses are
decorated in each line.
Cleanup overwritten methods of cmd.Cmd to allow proper shutdow in
interactive as well as in scripted mode.
Improved pylint score.
-rwxr-xr-x | mpm/tools/mpm_shell.py | 117 |
1 files changed, 83 insertions, 34 deletions
diff --git a/mpm/tools/mpm_shell.py b/mpm/tools/mpm_shell.py index 2997a5eb2..538d54586 100755 --- a/mpm/tools/mpm_shell.py +++ b/mpm/tools/mpm_shell.py @@ -9,6 +9,7 @@ RPC shell to debug USRP MPM capable devices """ from __future__ import print_function +import re import cmd import time import argparse @@ -51,6 +52,11 @@ def parse_args(): '-j', '--hijack', type=str, help="Hijack running session (excludes --claim)." ) + parser.add_argument( + '-s', '--script', type=str, + help="Run shell in scripting mode. Specified script contains " + "MPM shell commands, one per line." + ) return parser.parse_args() @@ -85,35 +91,35 @@ class MPMClaimer(object): """ from mprpc import RPCClient from mprpc.exceptions import RPCError - cmd = None + command = None token = None exit_loop = False client = RPCClient(host, port, pack_params={'use_bin_type': True}) try: while not exit_loop: - if token and not cmd: + if token and not command: client.call('reclaim', token) - elif cmd == 'claim': + elif command == 'claim': if not token: token = client.call('claim', 'MPM Shell') else: print("Already have claim") token_q.put(token) - elif cmd == 'unclaim': + elif command == 'unclaim': if token: client.call('unclaim', token) token = None token_q.put(None) - elif cmd == 'exit': + elif command == 'exit': if token: client.call('unclaim', token) token = None token_q.put(None) exit_loop = True time.sleep(1) - cmd = None + command = None if not cmd_q.empty(): - cmd = cmd_q.get(False) + command = cmd_q.get(False) except RPCError as ex: print("Unexpected RPC error in claimer loop!") print(str(ex)) @@ -152,6 +158,9 @@ class MPMClaimer(object): return self.token def hijack(self, token): + """ + Take over existing session by providing session token. + """ if self.token: print("Already have token") return @@ -163,7 +172,7 @@ class MPMShell(cmd.Cmd): """ RPC Shell class. See cmd module. """ - def __init__(self, host, port, claim, hijack): + def __init__(self, host, port, claim, hijack, script): cmd.Cmd.__init__(self) self.prompt = "> " self.client = None @@ -179,6 +188,9 @@ class MPMShell(cmd.Cmd): elif hijack: self.hijack(hijack) self.update_prompt() + self._script = script + if self._script: + self.parse_script() def _add_command(self, command, docs, requires_token=False): """ @@ -193,6 +205,9 @@ class MPMShell(cmd.Cmd): setattr(self, cmd_name, new_command) self.remote_methods.append(command) + def _print_response(self, response): + print(re.sub("^", "< ", response, flags=re.MULTILINE)) + def rpc_template(self, command, requires_token, args=None): """ Template function to create new RPC shell commands @@ -200,8 +215,9 @@ class MPMShell(cmd.Cmd): from mprpc.exceptions import RPCError if requires_token and \ (self._claimer is None or self._claimer.get_token() is None): - print("Cannot execute '{}' -- no claim available!".format(command)) - return + self._print_response("Cannot execute `{}' -- " + "no claim available!".format(command)) + return False try: if args or requires_token: expanded_args = self.expand_args(args) @@ -211,21 +227,20 @@ class MPMShell(cmd.Cmd): else: response = self.client.call(command) except RPCError as ex: - print("RPC Command failed!") - print("Error: {}".format(ex)) - return + self._print_response("RPC Command failed!\nError: {}".format(ex)) + return False except Exception as ex: - print("Unexpected exception!") - print("Error: {}".format(ex)) - return + self._print_response("Unexpected exception!\nError: {}".format(ex)) + return True if isinstance(response, bool): if response: - print("Command executed successfully!") + self._print_response("Command succeeded.") else: - print("Command failed!") + self._print_response("Command failed!") else: - print("==> " + str(response)) - return response + self._print_response(str(response)) + + return False def get_names(self): " We need this for tab completion. " @@ -234,13 +249,25 @@ class MPMShell(cmd.Cmd): ########################################################################### # Cmd module specific ########################################################################### - def run(self): - " Go, go, go! " - try: - self.cmdloop() - except KeyboardInterrupt: - self.do_disconnect(None) - exit(0) + def default(self, line): + self._print_response("*** Unknown syntax: %s" % line) + + def preloop(self): + """ + In script mode add Execution start marker to ease parsing script output + :return: None + """ + if self._script: + print("Execute %s" % self._script) + + def precmd(self, line): + """ + Add command prepended by "> " in scripting mode to ease parsing script + output. + """ + if self.cmdqueue: + print("> %s" % line) + return line def postcmd(self, stop, line): """ @@ -248,6 +275,7 @@ class MPMShell(cmd.Cmd): - Update prompt """ self.update_prompt() + return stop ########################################################################### # Internal methods @@ -257,7 +285,6 @@ class MPMShell(cmd.Cmd): Launch a connection. """ from mprpc import RPCClient - from mprpc.exceptions import RPCError print("Attempting to connect to {host}:{port}...".format( host=host, port=port )) @@ -342,6 +369,24 @@ class MPMShell(cmd.Cmd): claim_status=claim_status, ) + def parse_script(self): + """ + Adding script command from file pointed to by self._script. + + The commands are read from file one per line and added to cmdqueue of + parent class. This way they will be executed instead of input from + stdin. An EOF command is appended to the list to ensure the shell exits + after script execution. + :return: None + """ + try: + with open(self._script, "r") as script: + for command in script: + self.cmdqueue.append(command.strip()) + except OSError as ex: + print("Failed to read script. (%s)" % ex) + self.cmdqueue.append("EOF") # terminate shell after script execution + def expand_args(self, args): """ Takes a string and returns a list @@ -409,26 +454,30 @@ class MPMShell(cmd.Cmd): """import a python module into the global namespace""" globals()[args] = import_module(args) + # pylint: disable=invalid-name def do_EOF(self, _): - " When catching EOF, exit the program. " + """ + When catching EOF, exit the program. + """ print("Exiting...") self.disconnect() - exit(0) + return True # orderly shutdown def main(): " Go, go, go! " args = parse_args() - my_shell = MPMShell(args.host, args.port, args.claim, args.hijack) + my_shell = MPMShell(args.host, args.port, args.claim, + args.hijack, args.script) try: - return my_shell.run() + my_shell.cmdloop() except KeyboardInterrupt: my_shell.disconnect() - except Exception as ex: + except Exception as ex: # pylint: disable=broad-except print("Uncaught exception: " + str(ex)) my_shell.disconnect() + return False return True if __name__ == "__main__": exit(not main()) - |