diff --git a/docs/macros.md b/docs/macros.md new file mode 100644 index 0000000..a251a41 --- /dev/null +++ b/docs/macros.md @@ -0,0 +1,27 @@ +--- +title: User Macros +--- + +Users may define macros for `zti`. When defined, user macros appear in the list of available commands for `zti`. User macros are created by placing Python files in the ZTI macros directory, which defaults to the path `~/.zti-mac`. This path may be overridden with the `ZTI_MACROS_DIR` environment variables. + +User macro file names must take the form `my_macro.py`, and must contain a function with the signature, + +```python + def do_my_macro(zti, arg): + ... +``` + +This function is invoked when the macro command is run from `zti`. It is passed the current `zti` instance as the first argument and any arguments to the command as the second argument. + +The macro file may optionally also contain a function with the signature, + +```python + def help_my_macro(zti): + ... +``` + +This function will be registered as the help command for the main macro command. + +See `examples/macros/logon.py` for an example of a user macro. + +User macros which conflict with existing ZTI commands are ignored. \ No newline at end of file diff --git a/examples/macros/logon.py b/examples/macros/logon.py new file mode 100644 index 0000000..58122e4 --- /dev/null +++ b/examples/macros/logon.py @@ -0,0 +1,25 @@ +from tnz import ati + +def do_logon(zti, arg): + logon_setup() + ati.wait(lambda: ati.scrhas("Enter: ")) + ati.send("app1 userid[enter]") + ati.wait(lambda: ati.scrhas("Password ===> ")) + ati.send("password[enter]") + + # maybe look for a status message + ati.wait(lambda: ati.scrhas("ICH70001I")) + + # do something useful + ati.wait(lambda: ati.scrhas("***")) + ati.send("[enter]") + ati.wait(lambda: ati.scrhas("READY")) + +def logon_setup(): + ati.set("TRACE", "ALL") + ati.set("LOGDEST", "example.log") + + ati.set("ONERROR", "1") + ati.set("DISPLAY", "HOST") + ati.set("SESSION_HOST", "mvs1") + ati.set("SESSION", "A") diff --git a/tnz/zti.py b/tnz/zti.py index 46d4141..98989a1 100644 --- a/tnz/zti.py +++ b/tnz/zti.py @@ -57,6 +57,7 @@ import cmd import logging import os +import pathlib import platform import signal import sys @@ -173,6 +174,8 @@ def __init__(self, stdin=None, stdout=None): self.__install_plugins() + self.__register_macros() + # Methods def atexit(self): @@ -2704,6 +2707,73 @@ def __refresh(self): self.stdscr.refresh(_win_callback=self.__set_event_fn()) + def __register_macros(self): + import importlib.util + import sys + import types + + macros_dir = os.getenv("ZTI_MACROS_DIR") + if macros_dir is None: + macros_dir = os.path.expanduser("~/.zti-mac") + + if not os.path.isdir(macros_dir): + _logger.error(f"{macros_dir} is not a directory") + return + + for macro_file in os.listdir(macros_dir): + macro_file_path = pathlib.PurePath(macro_file) + + if macro_file_path.suffix != ".py": + continue + + macro_name = macro_file_path.stem + + # Ignore macros with uppercase letters or + # spaces + if ' ' in macro_name or not macro_name.islower(): + continue + + # Ignore macros which already exist + if f"do_{macro_name}" in self.get_names(): + _logger.warning(f"Function with name do_{macro_name}" + " already exists, macro registration" + " failed") + continue + + macro_path = os.path.join(macros_dir, macro_file) + + # Import the user macro as a module + macro_spec = importlib.util.spec_from_file_location( + f"module.{macro_name}", macro_path) + macro_module = importlib.util.module_from_spec(macro_spec) + sys.modules[f"module.{macro_name}"] = macro_module + macro_spec.loader.exec_module(macro_module) + + # Find the function + if hasattr(macro_module, f"do_{macro_name}"): + def do_macro(zti, arg): + self.__bg_wait_end() + macro_func = getattr(macro_module, + f"do_{macro_name}") + macro_func(zti, arg) + + # Create a new bound method for the `Zti` class for this + # function + setattr(Zti, f"do_{macro_name}", + types.MethodType( + do_macro, + self)) + + # Check if a corresponding help function exists + if hasattr(macro_module, f"help_{macro_name}"): + # Create a new bound method for the `Zti` class + # for this function + setattr(Zti, f"help_{macro_name}", + types.MethodType( + getattr(macro_module, + f"help_{macro_name}"), + self)) + def __scale_size(self, maxrow, maxcol): arows, acols = self.autosize aspect1 = arows / acols