Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions docs/macros.md
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.
30 changes: 30 additions & 0 deletions examples/macros/logon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from tnz.ati import ati

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it would actually be better to do from tnz import ati. that way the sample uses the current global ati object - which is what zti works with. when from tnz.ati import ati is used, the code here is bound the the global ati object at the time of import - there are situations in which that object can change.


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"))
Comment thread
najohnsn marked this conversation as resolved.
ati.send("logon[enter]")
ati.wait(lambda: ati.scrhas("LOGGED OFF"))

# disconnect from host
ati.drop("SESSION")

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")
72 changes: 72 additions & 0 deletions tnz/zti.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ def __init__(self, stdin=None, stdout=None):

self.__install_plugins()

self.__register_macros()

# Methods

def atexit(self):
Expand Down Expand Up @@ -2704,6 +2706,76 @@ 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.exception(f"{macros_dir} is not a directory")
Comment thread
najohnsn marked this conversation as resolved.
Outdated
return

for macro_file in os.listdir(macros_dir):
if not macro_file.endswith(".py"):
continue

if len(macro_file.split('.')) > 1:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest the use of pathlib for inspecting the filename and it's parts - this does not look right

continue

macro_name = macro_file.split('.')[0]
Comment thread
najohnsn marked this conversation as resolved.
Outdated

# Ignore macros with uppercase letters or
# spaces
uppercase = [letter for letter in
macro_name if letter.isupper()]
if ' ' in macro_name or len(uppercase) != 0:
Comment thread
najohnsn marked this conversation as resolved.
Outdated
continue

# Ignore macros which already exist
if f"do_{macro_name}" in self.get_names():
Comment thread
najohnsn marked this conversation as resolved.
_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
Expand Down