From b48c6d2ea432eb42eeaea9baee2aea29b7e4996c Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Sat, 25 Nov 2023 00:10:36 -0500 Subject: [PATCH] Add ipython shell to proxyclient Same functionality as the readline shell, but based on ipython so that it has nicer help/autocomplete/etc Signed-off-by: Daniel Berlin --- proxyclient/m1n1/ishell.py | 177 +++++++++++++++++++++++++++++++++ proxyclient/m1n1/ishell_ext.py | 24 +++++ proxyclient/tools/ishell.py | 11 ++ 3 files changed, 212 insertions(+) create mode 100644 proxyclient/m1n1/ishell.py create mode 100644 proxyclient/m1n1/ishell_ext.py create mode 100755 proxyclient/tools/ishell.py diff --git a/proxyclient/m1n1/ishell.py b/proxyclient/m1n1/ishell.py new file mode 100644 index 000000000..94b58fe16 --- /dev/null +++ b/proxyclient/m1n1/ishell.py @@ -0,0 +1,177 @@ +# SPDX-License-Identifier: MIT +import builtins +import sys + +import __main__ +from IPython import embed +from traitlets.config import get_config + +c = get_config() +c.InteractiveShellEmbed.colors = "Linux" +from inspect import signature + +from . import sysreg +from .proxy import * +from .proxyutils import * +from .utils import * + +__all__ = ["run_ishell"] + + +cmd_list = {} +subcmd_list = {} +# Debug levels +DBL_NONE = 0 +DBL_INFO = 1 +DBL_TRACE = 2 +DBL_DEBUG = 3 +DBL_EDEBUG = 4 + +db_level = DBL_NONE + + +def debug_cmd(db=None): + """Set debug level to integer %d(none)...%d(extreme debug)""" % ( + DBL_NONE, + DBL_EDEBUG, + ) + global db_level + if db: + db_level = db + print("debug level=%d" % db_level) + + +def help_cmd(arg=None): + if db_level >= DBL_DEBUG: + print("arg=%s" % repr(arg)) + if arg: + # cmd = arg.__qualname__ + if callable(arg): + cmd = arg.__name__ + elif isinstance(arg, str): + cmd = arg + else: + print("Unknown command: %s" % repr(arg)) + return + if db_level >= DBL_DEBUG: + print("cmd=%s" % repr(cmd)) + if cmd not in cmd_list: + print("Undocumented command %s" % cmd) + return + hinfo = cmd_list[cmd] + if isinstance(hinfo, str): + print("%-10s : %s" % (cmd, hinfo)) + return + if cmd in subcmd_list: + clist = subcmd_list[cmd] + aname = cmd + if db_level >= DBL_DEBUG: + print("subcmd_list[%s] = %s" % (repr(cmd), repr(clist))) + else: + print("command %s is not documented" % cmd) + return + else: + clist = cmd_list + aname = "top level" + print("Note: To display a category's commands quote the name e.g. help('HV')") + print("List of %s commands:" % aname) + for cmd in clist.keys(): + hinfo = clist[cmd] + if isinstance(hinfo, str): + msg = hinfo.strip().split("\n", 1)[0] + elif isinstance(hinfo, int): + msg = "%s category - %d subcommands" % (cmd, hinfo) + else: + print("%s ?" % cmd) + continue + if len(cmd) <= 10: + print("%-10s : %s" % (cmd, msg)) + else: + print("%s:\n %s" % (cmd, msg)) + + +# commands is a dictionary for constructing the +# InteractiveConsole with. It adds in the callables +# in proxy utils iface and sysreg into commands +def run_ishell(commands, msg=""): + saved_display = sys.displayhook + try: + + def display(val): + if isinstance(val, int) and not isinstance(val, bool): + builtins._ = val + print(hex(val)) + elif callable(val): + val() + else: + saved_display(val) + + sys.displayhook = display + + # convenience + commands["h"] = hex + commands["sysreg"] = sysreg + + if "proxy" in commands and "p" not in commands: + commands["p"] = commands["proxy"] + if "utils" in commands and "u" not in commands: + commands["u"] = commands["utils"] + + for obj_name in ("iface", "p", "u"): + obj = commands.get(obj_name) + obj_class = type(obj) + if obj is None: + continue + + for attr in dir(obj_class): + if attr in commands or attr.startswith("_"): + continue + + member = getattr(obj_class, attr) + if callable(member) and not isinstance(member, property): + cmd = getattr(obj, attr) + commands[attr] = cmd + + for attr in dir(sysreg): + commands[attr] = getattr(sysreg, attr) + + commands["help"] = help_cmd + commands["debug"] = debug_cmd + for obj_name in commands.keys(): + obj = commands.get(obj_name) + if obj is None or obj_name.startswith("_"): + continue + if callable(obj) and not isinstance(obj, property): + try: + desc = obj_name + str(signature(obj)) + except: + continue + qn = obj.__qualname__ + if qn.find(".") > 0: + a = qn.split(".") + if a[0] not in subcmd_list: + subcmd_list[a[0]] = {} + if a[0] not in cmd_list: + cmd_list[a[0]] = 1 + else: + cmd_list[a[0]] += 1 + clist = subcmd_list[a[0]] + else: + clist = None + if commands[obj_name].__doc__: + desc += " - " + commands[obj_name].__doc__ + cmd_list[obj_name] = desc + if isinstance(clist, dict): + clist[obj_name] = desc + + embed(config=c, extensions=["m1n1.ishell_ext"], header=msg, user_ns=commands) + finally: + sys.displayhook = saved_display + + +if __name__ == "__main__": + from .setup import * + + commands = dict(__main__.__dict__) + + run_ishell(commands, msg="Have fun!") diff --git a/proxyclient/m1n1/ishell_ext.py b/proxyclient/m1n1/ishell_ext.py new file mode 100644 index 000000000..e704029b7 --- /dev/null +++ b/proxyclient/m1n1/ishell_ext.py @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +# Extension for ipython to handle monitor polling +# and simd context after each executed line + + +class PostExecuteWatcher(object): + def __init__(self, ip): + self.shell = ip + + def post_execute(self): + mon = self.shell.user_ns.get("mon", None) + if mon != None: + try: + mon.poll() + except Exception as e: + print(f"mon.poll() failed: {e!r}") + u = self.shell.user_ns.get("u", None) + if u != None: + u.push_simd() + + +def load_ipython_extension(ip): + pew = PostExecuteWatcher(ip) + ip.events.register("post_execute", pew.post_execute) diff --git a/proxyclient/tools/ishell.py b/proxyclient/tools/ishell.py new file mode 100755 index 000000000..5710b019a --- /dev/null +++ b/proxyclient/tools/ishell.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +import pathlib +import sys + +sys.path.append(str(pathlib.Path(__file__).resolve().parents[1])) + +from m1n1.setup import * +from m1n1.ishell import run_ishell + +run_ishell(globals(), msg="Have fun!")