Wednesday, March 7, 2018

Creating your own commands, part 2

creating command validation program, help for the commands, and return value to calling program

In the last post, Creating your own commands, part 1, I wrote about a lot of the basics in creating your own IBM i commands. In this post I am going to show how to write a validation program for the command, how to add help, and return a value from a command.

I am going to create a validation program for the command with the file and library I showed in the previous post.

                   File & library (TESTCMD)

Type choices, press Enter.                                  

File . . . . . . . . . . . . . .                Name
  Library  . . . . . . . . . . .     *LIBL      Name, *LIBL

My validation program will check if the file entered exists. Like the program the command calls the validation program is passed one parameter for each parameter the command has. This command will pass a parameter, 20 characters long, that contains the file and library name.

01  PGM PARM(&FILELIB)

02  DCL VAR(&FILELIB) TYPE(*CHAR) LEN(20)
03  DCL VAR(&FILE) TYPE(*CHAR) LEN(10)
04  DCL VAR(&LIB) TYPE(*CHAR) LEN(10)
05  DCL VAR(&MSGTXT) TYPE(*CHAR) LEN(132)

06  MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR))

07  CHGVAR VAR(&FILE) VALUE(%SST(&FILELIB 1 10))
08  CHGVAR VAR(&LIB) VALUE(%SST(&FILELIB 11 10))

09  CHKOBJ OBJ(&LIB/&FILE) OBJTYPE(*FILE)

10  RETURN


11  ERROR: RCVMSG MSGTYPE(*EXCP) MSG(&MSGTXT)
12  CHGVAR VAR(&MSGTXT) VALUE('0000' || &MSGTXT)

13  SNDPGMMSG MSGID(CPD0006) MSGF(QCPFMSG) + 
                MSGDTA(&MSGTXT) MSGTYPE(*DIAG)

14  SNDPGMMSG  MSGID(CPF0002) MSGF(QCPFMSG) MSGTYPE(*ESCAPE)

15  ENDPGM

Line 1: As I mentioned above the command passes one parameter to this program as it has only one parameter.

Line 2 - 5: Definitions for the variables I will be using in this CL program.

Line 6: This is what I call a "generic" MONMSG command. As it is not preceded by any action command it applies to all commands in the program. When an error occurs the program goes to the ERROR tag, on line 11. I like structured program and do not like using GOTO, I would have like to call a subroutine, but with this being the "generic" MONMSG I can only use a go to command.

Lines 7 and 8: Extract the file and library name from the passed parameter, which contains both.

Line 9: I am using the Check Object command, CHKOBJ, to check if the file exists. If either the library of file does not exist an error happens, and the "generic" MONMSG takes processing to line 11. If the file is found then we continue.

line 10: This is line is only performed when the file is found. The RETURN command ends the program.

Line 11: This is where the error processing starts. It starts me retrieving message text from the last exception message from the current message queue.

Line 12: This line puts four zeros before the message text that was retrieved by the previous line. It is a bit hard to explain why until the next line is executed.

Line 13 and 14: If I want to the command to display a meaningful message I need to send these messages to the program message queue.

Line 13: Sending this message to the program message queue using the SNDPGMMSG command with CPD0006 writes it to the job's message queue.

Line 14: Sending CPF0002 to the program message queue with the message type of escape, MSGTYPE(*ESCAPE) will display the message at the command screen.

I compile this CL program just like any other.

There are two ways to set this new program to be the validation program for my command. This first is with the CRTCMD command. I press F10 for the additional parameters and the first one is the parameter for the validation program.

                 Create Command (CRTCMD)

Type choices, press Enter.

Command  . . . . . . . . . . . . > TESTCMD   
  Library  . . . . . . . . . . . >   MYLIB     
Program to process command . . . > TESTPGM   
  Library  . . . . . . . . . . . >   MYLIB     
Source file  . . . . . . . . . . > MYSRC     
  Library  . . . . . . . . . . . >   MYLIB     
Source member  . . . . . . . . .   *CMD      
Threadsafe . . . . . . . . . . .   *NO  

                      Additional Parameters

Validity checking program  . . . > VALPGM     
  Library  . . . . . . . . . . . >   MYLIB      

The trouble with defining the validation program at compile time is if I recompile the command and I forget to enter the validation program it is lost. I prefer to put the validation program in the CMD keyword at the start of my command.

01  CMD PROMPT('File & library') +
          VLDCKR(VALPGM)

The CL validation program has been successfully compiled and the command recompiled. Now when I use the command and enter an invalid file name I get the error message at the bottom of my command screen.

                           File & library (TESTCMD)

Type choices, press Enter.

File . . . . . . . . . . . . . . > NOT_A_FILE    Name
  Library  . . . . . . . . . . . >   QTEMP       Name, *LIBL




                                                               Bottom
F3=Exit   F4=Prompt   F5=Refresh   F12=Cancel   F13=How to use this d
F24=More keys
Object NOT_A_FILE in library QTEMP not found.

And on the job log.

TESTCMD FILE(QTEMP/NOT_A_FILE)
  0900 - CHKOBJ OBJ(QTEMP/NOT_A_FILE) OBJTYPE(*FILE)
  1100 - RCVMSG MSGTYPE(*EXCP) MSG(&MSGTXT)
  1300 - SNDPGMMSG MSGID(CPD0006) MSGF(QCPFMSG) MSGDTA('0000Object
  NOT_A_FILE in library QTEMP not found.') MSGTYPE(*DIAG)
Object NOT_A_FILE in library QTEMP not found.
  1400 - SNDPGMMSG MSGID(CPF0002) MSGF(QCPFMSG) MSGTYPE(*ESCAPE)
Error found on TESTCMD command.
Object NOT_A_FILE in library QTEMP not found.
Error found on TESTCMD command.

Of course, if there is no error the command processing program is called by the command.

How about giving my users some help with what to enter into the parameters of my command, help text. I have discussed using UIM to create help text before, therefore, I am not going to describe how to code it. Just how to implement it into a command. I created a help panel for the command I was adding validation too. I created a panel group of two panels. The first to be the extended help for the overall command, the second for the file parameter.

01  :PNLGRP.
    .***************************************************************
02  :HELP NAME='TESTCMD'.
03  Test Command.
04  :P.A description of the purpose of what this command is and what
05  it will do.
06  :EHELP.
    .***************************************************************
07  :HELP NAME='TESTCMD/FILE'.
08  :P.:HP2.File.:EHP2.
09  :P.Enter a valid file name.
10  :P.You can either a library name or leave the default *LIBL.
11  :EHELP.
12  .***************************************************************
13  :EPNLGRP.

Line 7: Notice that the help name is the command name qualified with the parameter. Unlike creating help for display files, I have to use format of: Command_name/Parameter_prompt_text.

Don't forget if you want to test your help panel before attaching it to the command you can use the QUHDSPH API to test.

I tell the command which help panel and the name of the extended help panel in the CMD keyword. When compile the compiler will map the help panel name for the File parameter itself.

01        CMD PROMPT('File & library') +           
                HLPID(TESTCMD) HLPPNLGRP(WA102HELP)

02        PARM KWD(SPACE) TYPE(Q1) MIN(1) PROMPT('File')

03  Q1:   QUAL TYPE(*NAME) LEN(10) MIN(1)               

04        QUAL TYPE(*NAME) LEN(10) DFT(*LIBL) +
                 SPCVAL((*LIBL)) PROMPT('Library')
                         SPCVAL((*LIBL)) PROMPT('Library')

When everything is compiled I can position my cursor onto the File parameter and press with Help or F1.

                   File & library (TESTCMD)

Type choices, press Enter.                                  

File . . . . . . . . . . . . . .                Name
  Library  . . . . . . . . . . .     *LIBL      Name, *LIBL

The help from the File parameter's help is shown.

Help                            File & library

    File.

    Enter a valid file name.

    You can either a library name or leave the default *LIBL.

If I press F2 to display the extended help.

Help                            Test Command.

    A description of the purpose of what this command is and what it
    will do.

    File.

    Enter a valid file name.

    You can either a library name or leave the default *LIBL.

The last example I will give is to return a value from a command's program to the calling program. My example command is going to be used to validate a tag number. The command will have three parameters:

  1. Tag group
  2. Tag number
  3. Return code to indicate if the Tag number is valid

The source for the command look like.

01  CMD PROMPT('Check Tag Number') ALLOW(*BPGM *IPGM)

02  PARM KWD(TAGGRP) TYPE(*CHAR) LEN(5) MIN(1) +
           PROMPT('Tag group')

03  PARM KWD(TAGNBR) TYPE(*CHAR) LEN(15) MIN(1) +
           PROMPT('Tag number')

04  PARM KWD(RTNCDE) TYPE(*CHAR) LEN(2) RTNVAL(*YES) +
           MIN(1) PROMPT('Return code')

Line 4: The return code parameter has the return Return Value keyword as yes, RTNVAL(*YES), which means a value will be returned in this parameter.

To return a value the command can only be run in a program or in REXX procedure. This is why I have the Where allowed to run keyword on line 1. I am allowing this command to only be run in interactive and batch programs. If I do not do this the command does not compile.

I can compile the command.

CRTCMD CMD(TAGLIB/TAGCHK) PGM(TAGLIB/TAG081) SRCFILE(TAGLIB/TAGSRC)

I have qualified the called program name with the library. This way I can use the command without needing to have TAGLIB in my library list.

I have decided that the command processing program, called by this command, is in RPG.

01  **free
02  ctl-opt main(Main) ;

03  dcl-pr Main extpgm('TAG081') ;
04    *n char(5) ;
05    *n char(15) ;
06    *n char(2) ;
07  end-pr ;

08  dcl-proc Main ;                             
09    dcl-pi *n ;
10      TagGrp char(5) ;
11      TagNbr char(15) ;
12      RtnCde char(2) ;
13    end-pi ;

14    if ((TagGrp = ' ') and (TagNbr = ' ')) ;
15      RtnCde = 'NO' ;
16    else ;
17      RtnCde = 'OK' ;
18    endif ;

19    return ;
20  end-proc ;

This program is a RPG using a Main procedure rather than a parameter list, it also does not use the RPG cycle. I am not going to go into the parts of this program in details I as wrote about using a Main procedure RPG program before.

Lines 3 – 7: Defines in a procedure definition the three parameters passed to and returned from this program.

Lines 9 – 13: This is the procedure interface, where I have given the three passed parameters names.

Lines 14 – 18: As this is just a test program the validation just checks that the first two parameters, Tag group and Tag number are not blank.

Line 19: The RETURN exits the main procedure and only the value for RtnCde is returned to the command, as it is the only parameter that allows a returned value.

I created a CL program of a few lines to execute this command.

01  PGM

02  DCL VAR(&RTNCDE) TYPE(*CHAR) LEN(2)

03  TAGLIB/TAGCHK TAGGRP(GRP01) TAGNBR(0703713) RTNCDE(&RTNCDE)

04  ENDPGM

Line 3: I have qualified the command with the library name, and as I qualified the called program name when I created the command I do not need TAGLIB in my library list.

Line 4: If I place a debug breakpoint before this line I can see that &RTNCDE contains "OK".

 

These two posts have not described everything you can do with commands, but I hope you now know a enough to be create your commands that you can use in your own applications.

 

You can learn more about creating commands on the IBM website here.

 

This article was written for IBM i 7.3, and should work for earlier releases too.

No comments:

Post a Comment

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.