Skip to content

Commit 8ab4e78

Browse files
author
John Craig
committed
Add support for user-defined macros
Signed-off-by: John Craig <john.craig@ibm.com>
1 parent bcde0d4 commit 8ab4e78

File tree

3 files changed

+127
-0
lines changed

3 files changed

+127
-0
lines changed

docs/macros.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
title: User Macros
3+
---
4+
5+
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.
6+
7+
User macro file names must take the form `my_macro.py`, and must contain a function with the signature,
8+
9+
```python
10+
def do_my_macro(zti, arg):
11+
...
12+
```
13+
14+
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.
15+
16+
The macro file may optionally also contain a function with the signature,
17+
18+
```python
19+
def help_my_macro(zti):
20+
...
21+
```
22+
23+
This function will be registered as the help command for the main macro command.
24+
25+
See `examples/macros/logon.py` for an example of a user macro.
26+
27+
A macro file name may not contain more than one period character. User macros which conflict with existing ZTI commands are ignored.

examples/macros/logon.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from tnz.ati import ati
2+
3+
def do_logon(zti, arg):
4+
logon_setup()
5+
ati.wait(lambda: ati.scrhas("Enter: "))
6+
ati.send("app1 userid[enter]")
7+
ati.wait(lambda: ati.scrhas("Password ===> "))
8+
ati.send("password[enter]")
9+
10+
# maybe look for a status message
11+
ati.wait(lambda: ati.scrhas("ICH70001I"))
12+
13+
# do something useful
14+
ati.wait(lambda: ati.scrhas("***"))
15+
ati.send("[enter]")
16+
ati.wait(lambda: ati.scrhas("READY"))
17+
ati.send("logon[enter]")
18+
ati.wait(lambda: ati.scrhas("LOGGED OFF"))
19+
20+
# disconnect from host
21+
ati.drop("SESSION")
22+
23+
def logon_setup():
24+
ati.set("TRACE", "ALL")
25+
ati.set("LOGDEST", "example.log")
26+
27+
ati.set("ONERROR", "1")
28+
ati.set("DISPLAY", "HOST")
29+
ati.set("SESSION_HOST", "mvs1")
30+
ati.set("SESSION", "A")

tnz/zti.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ def __init__(self, stdin=None, stdout=None):
173173

174174
self.__install_plugins()
175175

176+
self.__register_macros()
177+
176178
# Methods
177179

178180
def atexit(self):
@@ -2704,6 +2706,74 @@ def __refresh(self):
27042706

27052707
self.stdscr.refresh(_win_callback=self.__set_event_fn())
27062708

2709+
def __register_macros(self):
2710+
import importlib.util
2711+
import sys
2712+
import types
2713+
2714+
macros_dir = os.getenv("ZTI_MACROS_DIR")
2715+
if macros_dir is None:
2716+
macros_dir = os.path.expanduser("~/.zti-mac")
2717+
2718+
if not os.path.isdir(macros_dir):
2719+
_logger.exception(f"{macros_dir} is not a directory")
2720+
return
2721+
2722+
for macro_file in os.listdir(macros_dir):
2723+
if not macro_file.endswith(".py"):
2724+
continue
2725+
2726+
if len(macro_file.split('.')) > 1:
2727+
continue
2728+
2729+
macro_name = macro_file.split('.')[0]
2730+
2731+
# Ignore macros with uppercase letters or
2732+
# spaces
2733+
uppercase = [letter for letter in macro_name if letter.isupper()]
2734+
if ' ' in macro_name or len(uppercase) != 0:
2735+
continue
2736+
2737+
# Ignore macros which already exist
2738+
if f"do_{macro_name}" in self.get_names():
2739+
_logger.warning(f"Function with name do_{macro_name}"
2740+
" already exists, macro registration failed")
2741+
continue
2742+
2743+
macro_path = os.path.join(macros_dir, macro_file)
2744+
2745+
# Import the user macro as a module
2746+
macro_spec = importlib.util.spec_from_file_location(
2747+
f"module.{macro_name}", macro_path)
2748+
macro_module = importlib.util.module_from_spec(macro_spec)
2749+
sys.modules[f"module.{macro_name}"] = macro_module
2750+
macro_spec.loader.exec_module(macro_module)
2751+
2752+
# Find the function
2753+
if hasattr(macro_module, f"do_{macro_name}"):
2754+
def do_macro(zti, arg):
2755+
self.__bg_wait_end()
2756+
macro_func = getattr(macro_module,
2757+
f"do_{macro_name}")
2758+
macro_func(zti, arg)
2759+
2760+
# Create a new bound method for the `Zti` class for this
2761+
# function
2762+
setattr(Zti, f"do_{macro_name}",
2763+
types.MethodType(
2764+
do_macro,
2765+
self))
2766+
2767+
# Check if a corresponding help function exists
2768+
if hasattr(macro_module, f"help_{macro_name}"):
2769+
# Create a new bound method for the `Zti` class
2770+
# for this function
2771+
setattr(Zti, f"help_{macro_name}",
2772+
types.MethodType(
2773+
getattr(macro_module,
2774+
f"help_{macro_name}"),
2775+
self))
2776+
27072777
def __scale_size(self, maxrow, maxcol):
27082778
arows, acols = self.autosize
27092779
aspect1 = arows / acols

0 commit comments

Comments
 (0)