-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: User Macros #147
base: main
Are you sure you want to change the base?
Feature: User Macros #147
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
||
A macro file name may not contain more than one period character. User macros which conflict with existing ZTI commands are ignored. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
from tnz.ati import ati | ||
|
||
def 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")) | ||
ati.send("logon[enter]") | ||
ati.wait(lambda: ati.scrhas("LOGGED OFF")) | ||
|
||
# disconnect from host | ||
ati.drop("SESSION") | ||
|
||
def logon_helper(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unused function? |
||
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing newline |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ | |
ZTI_AUTOSIZE | ||
ZTI_SECLEVEL (see tnz.py) | ||
ZTI_TITLE | ||
ZTI_MACROS_DIR (~/.zti-mac is default) | ||
_BPX_TERMPATH (see _termlib.py) | ||
|
||
Copyright 2021, 2024 IBM Inc. All Rights Reserved. | ||
|
@@ -115,6 +116,11 @@ def __init__(self, stdin=None, stdout=None): | |
if os.getenv("ZTI_AUTOSIZE"): | ||
self.autosize = True | ||
|
||
self.macros_dir = os.getenv("ZTI_MACROS_DIR") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There seems to be no reason to make |
||
if self.macros_dir is None or not os.path.isdir( | ||
self.macros_dir): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't hide the error if the user has |
||
self.macros_dir = os.path.expanduser("~/.zti-mac") | ||
|
||
self.pend_intro = None | ||
self.__dirty_ranges = [] | ||
self.single_session = False | ||
|
@@ -169,6 +175,8 @@ def __init__(self, stdin=None, stdout=None): | |
|
||
self.__install_plugins() | ||
|
||
self.__register_macros() | ||
|
||
# Methods | ||
|
||
def atexit(self): | ||
|
@@ -2694,6 +2702,55 @@ def __refresh(self): | |
|
||
self.stdscr.refresh(_win_callback=self.__set_event_fn()) | ||
|
||
def __register_macros(self): | ||
import importlib.util | ||
import sys | ||
import types | ||
|
||
if not os.path.isdir(self.macros_dir): | ||
_logger.exception(f"{self.macros_dir} is not a directory") | ||
return | ||
|
||
for macro_file in os.listdir(self.macros_dir): | ||
if not macro_file.endswith(".py"): | ||
continue | ||
|
||
if len(macro_file.split('.')) > 1: | ||
continue | ||
|
||
macro_name = macro_file.split('.')[0] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since |
||
|
||
# Ignore macros which already exist | ||
if f"do_{macro_name}" in self.get_names(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. may want to consider a warning message |
||
continue | ||
|
||
macro_path = os.path.join(self.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}"): | ||
# Create a new bound method for the `Zti` class for this | ||
# function | ||
setattr(Zti, f"do_{macro_name}", | ||
types.MethodType( | ||
getattr(macro_module, f"do_{macro_name}"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You may have noticed that many of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another thing to model after with ati.ati.new_program(share_sessions=True):
plugin(arg, **kwargs) That helps ensure that the ati environment provided to the plugin is clean and that any variables set by the plugin do not leak into the current environment. Unless your use case requires non-shared/global ati variables as input to or output from the macro, I suggest being consistent with |
||
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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now need to rename to
do_logon