Skip to content
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

Generate binder source from module or service program #2485

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1699,6 +1699,13 @@
"category": "IBM i",
"icon": "$(debug)",
"enablement": "code-for-ibmi:testing"
},
{
"command": "code-for-ibmi.generateBinderSource",
"title": "Generate binder source",
"category": "IBM i",
"icon": "$(plus)",
"enablement": "code-for-ibmi:connected"
}
],
"keybindings": [
Expand Down Expand Up @@ -2312,6 +2319,10 @@
{
"command": "code-for-ibmi.searchIFSBrowser",
"when": "never"
},
{
"command": "code-for-ibmi.generateBinderSource",
"when": "never"
}
],
"view/title": [
Expand Down Expand Up @@ -2932,6 +2943,11 @@
"command": "code-for-ibmi.debug.setup.local",
"when": "!code-for-ibmi:debugManaged && view == ibmiDebugBrowser && viewItem =~ /^certificateIssue_localissue$/",
"group": "inline"
},
{
"command": "code-for-ibmi.generateBinderSource",
"when": "view == objectBrowser && viewItem =~ /^object.(module|srvpgm).*/",
Copy link
Collaborator

Choose a reason for hiding this comment

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

You may want to escape the dot here, since you'll be looking at an actual dot (and not any character).

Suggested change
"when": "view == objectBrowser && viewItem =~ /^object.(module|srvpgm).*/",
"when": "view == objectBrowser && viewItem =~ /^object\.(module|srvpgm).*/",

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you sure? The suggested change seems to be invalid syntax? Or am I missing something else here?

"group": "1_objActions@6"
}
],
"explorer/context": [
Expand Down
61 changes: 60 additions & 1 deletion src/api/IBMiContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import tmp from 'tmp';
import util from 'util';
import * as node_ssh from "node-ssh";
import { GetMemberInfo } from './components/getMemberInfo';
import { AttrOperands, CommandResult, IBMiError, IBMiMember, IBMiObject, IFSFile, QsysPath, SpecialAuthorities } from './types';
import { AttrOperands, CommandResult, IBMiError, IBMiMember, IBMiObject, IFSFile, ProgramExportImportInfo, ModuleExport, QsysPath, SpecialAuthorities } from './types';
import { FilterType, parseFilter, singleGenericName } from './Filter';
import { default as IBMi } from './IBMi';
import { Tools } from './Tools';
Expand Down Expand Up @@ -632,6 +632,65 @@ export default class IBMiContent {
});
}

/**
* @param object IBMiObject to get export and import info for
* @returns an array of ProgramExportImportInfo
*/
async getProgramExportImportInfo(library: string, name: string, type: string): Promise<ProgramExportImportInfo[]> {
if (!['*PGM', '*SRVPGM'].includes(type)) {
return [];
}
const results = await this.ibmi.runSQL(
[
`select PROGRAM_LIBRARY, PROGRAM_NAME, OBJECT_TYPE, SYMBOL_NAME, SYMBOL_USAGE,`,
` ARGUMENT_OPTIMIZATION, DATA_ITEM_SIZE`,
`from qsys2.program_export_import_info`,
`where program_library = '${library}' and program_name = '${name}' `,
`and object_type = '${type}'`
].join("\n")
);
if (results.length) {
return results.map(result => ({
program_library: result.PROGRAM_LIBRARY,
program_name: result.PROGRAM_NAME,
object_type: result.OBJECT_TYPE,
symbol_name: result.SYMBOL_NAME,
symbol_usage: result.SYMBOL_USAGE,
argument_optimization: result.ARGUMENT_OPTIMIZATION,
data_item_size: result.DATA_ITEM_SIZE
} as ProgramExportImportInfo));
} else {
return [];
}
janfh marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @param object IBMiObject to get module exports for
* @returns an array of ModuleExport
*/
async getModuleExports(library: string, name: string): Promise<ModuleExport[]> {
const outfile: string = Tools.makeid().toUpperCase();
const results = await this.runStatements(
`@DSPMOD MODULE(${library}/${name}) DETAIL(*EXPORT) OUTPUT(*OUTFILE) OUTFILE(QTEMP/${outfile})`,
[
`select EXLBNM as MODULE_LIBRARY, EXMONM as MODULE_NAME, EXMOAT as MODULE_ATTR, EXSYNM as SYMBOL_NAME,`,
` case EXSYTY when '0' then 'PROCEDURE' when '1' then 'DATA' end as SYMBOL_TYPE, EXOPPP as ARGUMENT_OPTIMIZATION`,
` from QTEMP.${outfile}`
].join("\n")
);
if (results.length) {
return results.map(result => ({
module_library: result.MODULE_LIBRARY,
module_name: result.MODULE_NAME,
module_attr: result.MODULE_ATTR,
symbol_name: result.SYMBOL_NAME,
symbol_type: result.SYMBOL_TYPE,
argument_optimization: result.ARGUMENT_OPTIMIZATION
} as ModuleExport));
}
return [];
janfh marked this conversation as resolved.
Show resolved Hide resolved
}

/**
*
* @param filter: the criterias used to list the members
Expand Down
74 changes: 74 additions & 0 deletions src/api/tests/suites/content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Tools } from '../../Tools';
import { posix } from 'path';
import IBMi from '../../IBMi';
import { newConnection, disposeConnection, CONNECTION_TIMEOUT } from '../connection';
import { ModuleExport, ProgramExportImportInfo } from '../../types';

describe('Content Tests', {concurrent: true}, () => {
let connection: IBMi
Expand Down Expand Up @@ -642,4 +643,77 @@ describe('Content Tests', {concurrent: true}, () => {
throw new Error(`Failed to create schema "${longName}"`);
}
});

it('getModuleExport', async () => {
const content = connection.getContent();
const config = connection.getConfig();
const tempLib = config!.tempLibrary;
const id: string = `${Tools.makeid().toUpperCase()}`;
const source: string = `/tmp/vscodetemp-${id}.clle`;
janfh marked this conversation as resolved.
Show resolved Hide resolved
await content.runStatements(
`CALL QSYS2.IFS_WRITE(PATH_NAME =>'${source}',
LINE => 'PGM',
OVERWRITE => 'NONE',
END_OF_LINE => 'CRLF')`,
`CALL QSYS2.IFS_WRITE(PATH_NAME =>'${source}',
LINE => 'ENDPGM',
OVERWRITE => 'APPEND',
END_OF_LINE => 'CRLF')`,
`@CRTCLMOD MODULE(${tempLib}/${id}) SRCSTMF('${source}')`,
`select 1 from sysibm.sysdummy1`
);
let exports: ModuleExport[] = await content.getModuleExports(tempLib, id);

expect(exports.length).toBe(1);
expect(exports.at(0)?.symbol_name).toBe(id);

await connection!.runCommand({
command: `DLTMOD MODULE(${tempLib}/${id})`,
environment: 'ile'
});
janfh marked this conversation as resolved.
Show resolved Hide resolved
await connection!.runCommand({
command: `DEL OBJLNK('${source}')`,
environment: 'ile'
});
});

it('getProgramExportImportInfo', async () => {
const content = connection.getContent();
const config = connection.getConfig();
const tempLib = config!.tempLibrary;
const id: string = `${Tools.makeid().toUpperCase()}`;
const source: string = `/tmp/vscodetemp-${id}.clle`;
janfh marked this conversation as resolved.
Show resolved Hide resolved
await content.runStatements(
`CALL QSYS2.IFS_WRITE(PATH_NAME =>'${source}',
LINE => 'PGM',
OVERWRITE => 'NONE',
END_OF_LINE => 'CRLF')`,
`CALL QSYS2.IFS_WRITE(PATH_NAME =>'${source}',
LINE => 'ENDPGM',
OVERWRITE => 'APPEND',
END_OF_LINE => 'CRLF')`,
`@CRTCLMOD MODULE(${tempLib}/${id}) SRCSTMF('${source}')`,
`@CRTSRVPGM SRVPGM(${tempLib}/${id}) MODULE(${tempLib}/${id}) EXPORT(*ALL)`,
`select 1 from sysibm.sysdummy1`
);

const info: ProgramExportImportInfo[] = (await content.getProgramExportImportInfo(tempLib, id, '*SRVPGM'))
.filter(info => info.symbol_usage === '*PROCEXP');

expect(info.length).toBe(1);
expect(info.at(0)?.symbol_name).toBe(id);

await connection!.runCommand({
command: `DLTSRVPGM SRVPGM(${tempLib}/${id})`,
environment: 'ile'
});
await connection!.runCommand({
command: `DLTMOD MODULE(${tempLib}/${id})`,
environment: 'ile'
});
janfh marked this conversation as resolved.
Show resolved Hide resolved
await connection!.runCommand({
command: `DEL OBJLNK('${source}')`,
environment: 'ile'
});
});
});
19 changes: 19 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,23 @@ export type SearchHitLine = {
content: string
}

export interface ProgramExportImportInfo {
program_library: string,
program_name: string,
object_type: string,
symbol_name: string,
symbol_usage: string,
argument_optimization: string,
data_item_size: number
}

export interface ModuleExport {
module_library: string,
module_name: string,
module_attr: string,
symbol_name: string,
symbol_type: string,
argument_optimization: string,
}
janfh marked this conversation as resolved.
Show resolved Hide resolved

export * from "./configuration/config/types";
23 changes: 22 additions & 1 deletion src/ui/views/objectBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Search } from "../../api/Search";
import { Tools } from "../../api/Tools";
import { getMemberUri } from "../../filesystems/qsys/QSysFs";
import { instance } from "../../instantiate";
import { CommandResult, DefaultOpenMode, FilteredItem, FocusOptions, IBMiMember, IBMiObject, MemberItem, OBJECT_BROWSER_MIMETYPE, ObjectFilters, ObjectItem, WithLibrary } from "../../typings";
import { CommandResult, DefaultOpenMode, FilteredItem, FocusOptions, IBMiMember, IBMiObject, MemberItem, ModuleExport, OBJECT_BROWSER_MIMETYPE, ObjectFilters, ObjectItem, ProgramExportImportInfo, WithLibrary } from "../../typings";
import { editFilter } from "../../webviews/filters";
import { VscodeTools } from "../Tools";
import { BrowserItem, BrowserItemParameters } from "../types";
Expand Down Expand Up @@ -541,6 +541,27 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) {
objectTreeViewer.reveal(item, options);
}),

vscode.commands.registerCommand(`code-for-ibmi.generateBinderSource`, async (node: ObjectBrowserObjectItem) => {
const contentApi = getContent();
let exports: ProgramExportImportInfo[] | ModuleExport[] = [];
if (node.object.type === '*MODULE') {
exports = (await contentApi.getModuleExports(node.object.library, node.object.name))
.filter(exp => exp.symbol_type === 'PROCEDURE');
} else {
exports = (await contentApi.getProgramExportImportInfo(node.object.library, node.object.name, node.object.type))
.filter(info => info.symbol_usage === '*PROCEXP');
}
const content = [
`/* Binder source generated from ${node} */`,
``,
worksofliam marked this conversation as resolved.
Show resolved Hide resolved
`STRPGMEXP PGMLVL(*CURRENT) /* SIGNATURE("") */`,
...exports.map(info => ` EXPORT SYMBOL("${info.symbol_name}")`),
`ENDPGMEXP`,
].join("\n");
const textDoc = await vscode.workspace.openTextDocument({ language: 'bnd', content });
await vscode.window.showTextDocument(textDoc);
}),

vscode.commands.registerCommand(`code-for-ibmi.createMember`, async (node: ObjectBrowserSourcePhysicalFileItem, fullName?: string) => {
const connection = getConnection();
const toPath = (value: string) => connection.upperCaseName(`${node.path}/${value}`);
Expand Down
Loading