Skip to content

Add extension that runs REXX over ZOWE CLI #122

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions code4z/example-com-extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.DS_Store
node_modules
*.vsix
*.zip
15 changes: 15 additions & 0 deletions code4z/example-com-extension/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
}
]
}
3 changes: 3 additions & 0 deletions code4z/example-com-extension/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}
Empty file.
65 changes: 65 additions & 0 deletions code4z/example-com-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# VS Code extension for the example.com company

This is an example of an extension a company `example.com` may want to create to supplement Code4z with custom, company specific functionality.

## Making your custom ISPF apps available in VS Code

If you have custom ISPF panels for your internal processes you may want to make their functionality available in VS Code as part of your DevOps modernization.

The following sections outline the steps to take.

### Update your ISPF application to run in plain TSO

1. Update your ISPF application to accept arguments instead of relying on screen inputs
1. Update the application to store its output to a dataset, or print it to the terminal instead of showing the result on an ISPF screen
1. Test your updated application by running it in TSO without ISPF and check its output.
1. Run your updated application through ZOWE CLI via the following command `zowe tso issue command "exec 'PUBLIC.REXX(REPOUT)' 'ARG1 ARG2'"`, where `PUBLIC.REXX(REPOUT)` is your REXX application and `ARG1` `ARG2` are the arguments that are passed to it. The syntax above works both in Windows CMD, Powershell as well as bourne compatible UNIX shells.

### Use the basic-report command in this extension

1. Update the `REXX_EXEC` constant in [basic-report.js](commands/basic-report.js#L6) to point to your application.
1. Start the extension by pressing `F5`. This will start a new VS Code window with this extension.
1. In this new VS Code window open the _Command Palette_ by pressing `F1`
1. Type `example.com` in the command palette input box

Now you should see ![Command Pallete](command-palette.png)

1. Select the `Basic Report on a Dataset` command
1. After a short moment an editor with the output of your applications will open

If you would like to try this out with a basic REXX program, you can use the included [basic-report.rexx](commands/basic-report.rexx) sample. The output should look similar to ![Report](report.png)

### Explore the ehnanced-report
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
### Explore the ehnanced-report
### Explore the enhanced-report


The [basic report](commands/basic-report.js) is only 30 lines long. It is as simple as possible to get started quickly. To make the extension real there is a lot more to do. For example:

- Input validation
- Error checking
- Remembering last entry
- Adding a progress bar
- Storeing the report to a dataset and retrieving it from there
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
- Storeing the report to a dataset and retrieving it from there
- Storing the report to a dataset and retrieving it from there

- Adding a VS Code Output channel to diagnose issues
- Adding a setting for the location of the REXX exec instead of hard coding it in the extension code

All of these ehnancements have been added to the [enhanced report](commands/enhanced-report.js) with its corresponding [enhanced-report.rexx](commands/enhanced-report.rexx) REXX exec. This adds a little over 100 lines of code and illustrated many other useful VS Code APIs as well as adding typescript checking via [JS Doc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) annotations.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
All of these ehnancements have been added to the [enhanced report](commands/enhanced-report.js) with its corresponding [enhanced-report.rexx](commands/enhanced-report.rexx) REXX exec. This adds a little over 100 lines of code and illustrated many other useful VS Code APIs as well as adding typescript checking via [JS Doc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) annotations.
All of these enhancements have been added to the [enhanced report](commands/enhanced-report.js) with its corresponding [enhanced-report.rexx](commands/enhanced-report.rexx) REXX exec. This adds a little over 100 lines of code and illustrated many other useful VS Code APIs as well as adding typescript checking via [JS Doc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) annotations.


### Build the extension

The extension can be build by running the two following commands
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
The extension can be build by running the two following commands
The extension can be built by running the two following commands


```
# Install development dependencies - typescript, types, and vsce
npm install
# Package the extension
npm run package
```

### Next steps

A few ideas about what you may want to try next:

- Remember last 10 user inputs and let them choose (in addition to typing a new one)
- Submit a JOB and retrieve its output instead of running a REXX exec
- Use the ZOWE SDK instead of ZOWE CLI (remove run-time dependency)
- Execute REXX exec over SSH or submit a JOB over FTP if you do not have ZOWE CLI available
- Copy the REXX exec to the mainframe before a command runs (in case the REXX does not exist) - self deploy
Binary file added code4z/example-com-extension/command-palette.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions code4z/example-com-extension/commands/basic-report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// @ts-nocheck
const vscode = require('vscode');
const util = require('node:util');
const execFile = util.promisify(require('node:child_process').execFile);

const REXX_EXEC = "PUBLIC.REXX(BASIC)";

const simpleReport = context => async () => {
await vscode.workspace.fs.createDirectory(context.globalStorageUri);
const reportUri = vscode.Uri.joinPath(context.globalStorageUri, "report.txt");

const dsn = await vscode.window.showInputBox({
placeHolder: 'Please enter a name of a PDS to inquire'
});

const output = await execRexx(dsn);
await vscode.workspace.fs.writeFile(reportUri, Buffer.from(output, 'utf-8'));

const document = await vscode.workspace.openTextDocument(reportUri);
await vscode.window.showTextDocument(document);
}

async function execRexx(dsn) {
const { stdout, stderr } = await execFile('zowe', ["tso", "issue", "command", `exec '${REXX_EXEC}' '${dsn}'`]);
return stdout;
}

module.exports = {
simpleReport
}
15 changes: 15 additions & 0 deletions code4z/example-com-extension/commands/basic-report.rexx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* REXX */
parse arg dsn

say "==========================================================="
say "|"
say "| REPORT FROM RUNNING 'LISTDS' MEMBER ON:"
say "|"
say "| " dsn
say "|"
say "==========================================================="
say " "

"LISTDS '"dsn"' MEMBERS"

EXIT
137 changes: 137 additions & 0 deletions code4z/example-com-extension/commands/enhanced-report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
const vscode = require('vscode');
const util = require('node:util');
const execFile = util.promisify(require('node:child_process').execFile);

const CONFIG_PREFIX = 'example-com';
const REPORT_EXEC = 'reportExec';

/**
* @type {vscode.ExtensionContext}
*/
let context;

/**
* @type {vscode.LogOutputChannel}
*/
let log;

/**
* @param {vscode.ExtensionContext} ctx
* @param {vscode.LogOutputChannel} lg
*/

function registerReportCommand(ctx, lg) {
context = ctx;
log = lg;
return reportCommand;
}

async function reportCommand() {
await vscode.workspace.fs.createDirectory(context.globalStorageUri);
const exec = vscode.workspace.getConfiguration(`${CONFIG_PREFIX}`).get(REPORT_EXEC);
if (!exec) {
const response = await vscode.window.showInformationMessage("Report exec setting missing, canceling action", 'Open Settings');
if (response == 'Open Settings') {
vscode.commands.executeCommand('workbench.action.openSettings', `${CONFIG_PREFIX}.${REPORT_EXEC}`);
}
return;
}
let arg = await vscode.window.showInputBox({
placeHolder: 'Please enter a name of a PDS to inquire',
value: context.globalState.get('arg'),
validateInput: isValidDsn
});
if (!arg) {
vscode.window.showInformationMessage("No dataset name provided, canceling action");
return;
}
arg = arg.toUpperCase();
context.globalState.update('arg', arg);
const reportFileName = `report_on_${arg.toUpperCase()}.txt`;
const reportFileUri = vscode.Uri.joinPath(context.globalStorageUri, reportFileName);
// log.show();
await vscode.window.withProgress({
title: `Reporting on ${arg}`,
location: vscode.ProgressLocation.Notification,
cancellable: false
}, async (progress, _token) => {
progress.report({ increment: 20, message: `Execuring Report` });
const reportOutputDsn = await executeReport(exec, arg);
if (!reportOutputDsn) {
vscode.window.showInformationMessage("No report output provided");
return;
}
progress.report({ increment: 30, message: `Downloading Report` });
await downloadReport(reportOutputDsn, reportFileUri);

});

const reportUri = vscode.Uri.from({ scheme: ReportProvider.scheme, path: reportFileName });
await openReport(reportUri);
}

/**
* @param {string} exec rexx or clist to execute
* @param {string} arg argument to the report exec
*/

async function executeReport(exec, arg) {
const { stdout, stderr } = await execFile('zowe', ["tso", "issue", "command", `exec '${exec}' '${arg}'`]);
log.info(`Executing REXX exec: ${exec} ${arg}`);
stdout.split("\n").forEach(line => line && log.info(line));
stderr.split("\n").forEach(line => line && log.error(line));
const dsnLine = stdout.split("\n").find(line => line.match(/^DSN=/));
const dsn = dsnLine?.replace('DSN=', '');
return dsn;
}
/**
* @param {string} reportDsn dataset from which to download report
* @param {vscode.Uri} reportUri file uri where to store the downloaded report
*/

async function downloadReport(reportDsn, reportUri) {
const { stdout, stderr } = await execFile('zowe', ["files", "download", "data-set", reportDsn, "-f", reportUri.fsPath]);
log.info(`Downloading report from ${reportDsn} to ${reportUri}`);
stdout.split("\n").forEach(line => line && log.info(line));
stderr.split("\n").forEach(line => line && log.error(line));
}
/**
* @implements {vscode.TextDocumentContentProvider}
*/
class ReportProvider {
static scheme = "com-example+report";
/**
* @param {vscode.Uri} uri
* @param {vscode.CancellationToken} token
*/
async provideTextDocumentContent(uri, token) {
const fileUri = vscode.Uri.joinPath(context.globalStorageUri, uri.path);
const content = await vscode.workspace.fs.readFile(fileUri);
return content.toString();
}
}

/**
* @param {vscode.Uri} reportUri
*/

async function openReport(reportUri) {
const document = await vscode.workspace.openTextDocument(reportUri);
await vscode.window.showTextDocument(document);
}
/**
*
* @param {string} input
*/
function isValidDsn(input) {
const datasetPattern = /^([A-Za-z@#$][0-9A-za-z@#$]{0,7}\.)+([A-Za-z@#$][0-9A-Za-z@#$]{0,7})$/;
if (input.length <= 44 && input.match(datasetPattern)) {
return;
}
return 'Please, enter a valid dataset name';
}

module.exports = {
registerReportCommand,
ReportProvider
}
35 changes: 35 additions & 0 deletions code4z/example-com-extension/commands/enhanced-report.rexx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* REXX */
PARSE ARG dsname
OUTPUT = userid()||'.PUBLIC.JCLOUT(REPORT)'

flower.0 = 0
call prnt "==========================================================="
call prnt "|"
call prnt "| REPORT FROM RUNNING 'LISTDS' MEMBER ON:"
call prnt "|"
call prnt "| " dsname
call prnt "|"
call prnt "==========================================================="
call prnt " "

X = OUTTRAP("memlst.")
"LISTDS '"dsname"' MEMBERS"
X = OUTTRAP("OFF")

"ALLOC FI(SYSUT2) DA('"||OUTPUT||"') SHR REUSE"
"EXECIO 0 DISKW SYSUT2 (OPEN"
"EXECIO * DISKW SYSUT2 (STEM flower."
"EXECIO * DISKW SYSUT2 (STEM memlst."
"EXECIO 0 DISKW SYSUT2 (FINIS"
"FREE FI(SYSUT2)"

SAY "DSN='"||OUTPUT||"'"
EXIT

prnt:
arg text
i = flower.0
i = i+1
flower.i = text
flower.0 = i
return
11 changes: 11 additions & 0 deletions code4z/example-com-extension/commands/open-docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const vscode = require('vscode');
const url = 'https://techdocs.broadcom.com/us/en/ca-mainframe-software/devops/code4z/2-0.html';

async function openDocsCommand() {
const docsUrl = vscode.Uri.parse(url);
vscode.env.openExternal(docsUrl);
}

module.exports = {
openDocsCommand
}
34 changes: 34 additions & 0 deletions code4z/example-com-extension/extension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const vscode = require('vscode');
const { registerReportCommand, ReportProvider } = require("./commands/enhanced-report");
const { openDocsCommand } = require('./commands/open-docs');
const { simpleReport } = require('./commands/basic-report');

/**
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
const log = vscode.window.createOutputChannel('Example.com', { log: true });

// Report on Dataset
const reportDisp = vscode.commands.registerCommand('com.example.report.on.dataset', registerReportCommand(context, log));
const provider = new ReportProvider;
const providerRegistration = vscode.workspace.registerTextDocumentContentProvider(ReportProvider.scheme, provider);
context.subscriptions.push(reportDisp, providerRegistration);

// Simple Report
const simpleReportDisp = vscode.commands.registerCommand('com.example.simple.report', simpleReport(context));
context.subscriptions.push(simpleReportDisp);

// Open Docs
const openDocsDisp = vscode.commands.registerCommand('com.example.open.docs', openDocsCommand);
context.subscriptions.push(openDocsDisp);

console.log('Congrats, your com.example extension is now active!');
}

function deactivate() { }

module.exports = {
activate,
deactivate
}
Loading