by Ernie Malaga
Commands are much more powerful than you may first think.
Last month we learned how commands are created, and discussed some of the techniques used in them. With this article, we will study a few more advanced techniques that can further develop your own commands and make them the useful tools they are intended to be.
Two Commands Can Share The CPP
As you saw last month, each command must have a Command Processing Program (CPP) which actually does the work associated with the command. You may have the impression, then, that each command must have a different CPP. If this were true it would mean, for example, that the commands to create a user profile and to change a user profile (CRTUSRPRF and CHGUSRPRF) would require two CPPs, even though they would probably perform the same input validation on the same objects. This would be a waste. Fortunately, commands can give you the flexibility of using one CPP for two or more commands by way of constant parameters and zero-element parameters. Let's see what these are.
A constant parameter is never prompted for, and you may not assign any value to it, yet it is always passed to the CPP. The obvious use for this kind of parameter would be to let a single CPP know which command activated it. We will create two commands, Start System Printer (STRSYSPRT) and End System Printer (ENDSYSPRT), to illustrate this technique. STRSYSPRT and ENDSYSPRT are shown in1a and 1b, respectively. Their shared CPP is shown in 1c.
A constant parameter is never prompted for, and you may not assign any value to it, yet it is always passed to the CPP. The obvious use for this kind of parameter would be to let a single CPP know which command activated it. We will create two commands, Start System Printer (STRSYSPRT) and End System Printer (ENDSYSPRT), to illustrate this technique. STRSYSPRT and ENDSYSPRT are shown in Figures 1a and 1b, respectively. Their shared CPP is shown in Figure 1c.
Both commands are to be created indicating SYS001CL as their CPP. When either command is executed, SYS001CL receives into the &CMDNAME variable a constant, either 'STRSYSPRT' or 'ENDSYSPRT', depending on which command activated it. The CPP then retrieves system value QPRTDEV, which contains the name of the system printer. Finally, if the command run was STRSYSPRT, the standard Start Printer Writer (STRPRTWTR) command is executed. If the command run was ENDSYSPRT, the standard command End Writer (ENDWTR) is executed. We have saved ourselves from having to create two different programs which in real-life cases are bound to be much more involved, although almost identical to each other.
Using zero-element parameters is another way you can share CPPs among commands. A zero-element parameter is the opposite of a constant parameter: it never has any value, and you cannot assign it any, but the CPP expects a parameter just the same. This can be useful when one of the commands that share a CPP has more parameters than another. In these cases, zero-element parameters can be used as "place holders" to ensure that all commands sharing the same CPP have the same number of parameters. Let's illustrate again with the STRSYSPRT and ENDSYSPRT commands we defined above. We will change the ENDSYSPRT to add an option, *CNTRLD or *IMMED, for the ending of the printer writer. The new commands and CPP are shown in Figures 2a, 2b, and 2c respectively.
The STRSYSPRT command has two parameters, but neither shows when the prompter is activated (F4 key) because the first parameter is a constant and the second parameter is TYPE(*ZEROELEM). For all intents and purposes, it is as if the STRSYSPRT command had no parameters. The second parameter is nothing but a "place holder" needed because the CPP expects two parameters instead of one.
The ENDSYSPRT command also has two parameters, but only one shows when the prompter is activated because the first parameter is a constant. To the user, then, it is as if ENDSYSPRT had only one parameter.
The revised program SYS001CL must nevertheless receive two parameters from either command. If the STRSYSPRT command was executed, parameter &OPTION will not have a value (presumably, it will be all blanks).
Controlling The Command Prompter
Sooner or later you will begin creating commands that have many parameters, and it is likely that most of the parameters will be seldom used. The command prompter can be controlled in such a way that the less useful parameters can be "hidden" and kept out of the way. Doing this reduces the amount of clutter on the display. For those cases when you must enter a value in one of these parameters, however, you can press the F10 key to request additional parameters.
This is done by adding PMTCTL(*PMTRQS) to the PARM statement of each parameter to be so treated. Next time you prompt for the command, the prompter will display only the parameters that do not have PMTCTL(*PMTRQS) specified, and will enable the F10 key with the legend "More Parameters." When pressed, the prompter will add the parameters that have PMTCTL(*PMTRQS).
This technique is simple to implement. There is another technique, more advanced, which involves the PMTCTL statement. The PMTCTL statement is used if a parameter is to be prompted only when a condition is met. For example, let's create a command to save a file on magnetic media. We will call this command SAVFILE, and it will be capable of saving files on either diskette or tape. Note that when an object is saved on tape, you must also indicate whether the tape should be unloaded, rewound, or left at the point where it ended. This "option" has no meaning on diskette. The SAVFILE command is shown in 3. The CPP is omitted because it is irrelevant.
This technique is simple to implement. There is another technique, more advanced, which involves the PMTCTL statement. The PMTCTL statement is used if a parameter is to be prompted only when a condition is met. For example, let's create a command to save a file on magnetic media. We will call this command SAVFILE, and it will be capable of saving files on either diskette or tape. Note that when an object is saved on tape, you must also indicate whether the tape should be unloaded, rewound, or left at the point where it ended. This "option" has no meaning on diskette. The SAVFILE command is shown in Figure 3. The CPP is omitted because it is irrelevant.
Let's analyze this command definition. The first parameter is the name of the file and is mandatory as indicated by MIN(1). The second parameter, MEDIA, can only have the restricted values *DKT and *TAP, of which *DKT is the default. The third parameter, ENDOPT, can only have the values *LEAVE, *REWIND, or *UNLOAD, having *REWIND as the default. But notice that PMTCTL(PC1) has been added. It says that there is a prompt control statement at label PC1. In fact, label PC1 points to a PMTCTL statement: the controlling parameter is MEDIA. If this parameter is equal to *TAP, the condition is satisfied, so the ENDOPT parameter is prompted for.
When you execute this command using the prompter, the system will ask only for the name of the file and the media type. If and only if you enter *TAP for the media type and press Enter, the prompter will ask you for the end-of-volume option. Prompting for a parameter can be controlled using more than one PMTCTL statement per parameter, and any number of parameters can share the same set of PMTCTL statements, provided that they ought to be prompted for under the same circumstances.
Commands That Return Values
Suppose that you have a small RPG program that calculates the day of the week for any given date. This program receives the date in one parameter - a packed decimal number six digits long with no decimals. It returns the day of the week in the second parameter, which is character, three bytes long (SUN, MON, TUE, and so on).
Suppose now that in your CL programs you need, now and then, to know the day of the week of a date that is stored in a CL variable. You could call the RPG program using a CALL statement, but it is just as easy to create a command that will return the day of the week into a CL program variable. Such command (CLCDAYWEK) is shown in 4.
Suppose now that in your CL programs you need, now and then, to know the day of the week of a date that is stored in a CL variable. You could call the RPG program using a CALL statement, but it is just as easy to create a command that will return the day of the week into a CL program variable. Such command (CLCDAYWEK) is shown in Figure 4.
At a glance we can see that the DATE parameter is mandatory, decimal, six digits, and no decimal places. Parameter DOW (Day Of Week) requires a little more explaining. RTNVAL(*YES) indicates that this parameter will have a value returned by the CPP, while CHOICE shows what the prompter will place to the right of the input field.
Compiling this command is somewhat different than compiling all the others. You must be sure to add ALLOW(*IPGM *BPGM). This will tell the system that the CLCDAYWEK command should be allowed only in interactive programs or batch programs, but not in any other mode such as interactively from the keyboard, or invoked by an HLL program through QCMDEXC. (Refer to the CRTCMD command in the CL Reference Manual.)
When included in a CL program, the CLCDAYWEK command would look like that in 5.
When included in a CL program, the CLCDAYWEK command would look like that in Figure 5.
More About Command Data Types
As I mentioned last month, commands have more data types than CL programs. It is important to understand each data type so that you can select the right one at the right time. It is also important to know the format in which the CPP will receive the parameters. Pages 9-8 and 9-9 of the CL Programmer's Guide offer an excellent description of each data type under the heading "Parameter Types."
If a parameter is declared as TYPE(*DATE), the command prompter will reject any input that does not represent a valid date according to system value QDATFMT. This validation is performed for you at the command level, so you do not have to repeat it in your program. The only catch is that the CPP receives the date parameter as a seven-byte, character value, always in the format CYYMMDD, with C representing the century (0=Current, 1=Next), and YYMMDD representing the date itself.
Similarly, a parameter declared as TYPE(*TIME) will not be allowed to have invalid time values in the format HHMMSS (hours, minutes, and seconds). The CPP will receive the parameter in a six-byte, character variable.
Limiting Parameter Values The value of a parameter can be limited to a certain set by using the restricted values indication, RSTD(*YES). When this keyword is added in the PARM statement, the VALUES( ) keyword must also be added, and all valid values must be included within the parentheses. This technique has been shown before, but it is worth reviewing. Suppose you need to create a command that will optionally print something. You could define a parameter with keyword PRINT and have it restricted to the values *YES and *NO, like this:
PARM KWD(PRINT) TYPE(*CHAR) +
LEN(4) RSTD(*YES) +
VALUES(*YES *NO) +
PROMPT('Print report')
There is a better technique if the values you consider valid fall within a certain range. It involves the use of the RANGE keyword. Again, if you were to specify PRINT(*YES) in the fictitious command half-described above, the prompter could ask for the number of copies. Because you are mindful of computer supply costs, you want to limit the number of copies to six, so you add the following parameter:
PARM KWD(PRINT) TYPE(*CHAR) +
LEN(4) RSTD(*YES) +
VALUES(*YES *NO) +
PROMPT('Print report')
About Special Values and Single Values
First of all, what is a special value? You have seen them many times: they are the ones that begin with an asterisk (*). OS/400 commands are riddled with special values. Simply speaking, a special value is one with a predetermined meaning.
Consider the case of a parameter that asks for a library name, for instance. You can supply an actual library name, such as QSYS, a variable name, such as &LIBNAM (if you are writing a CL program), or a special value, such as *CURLIB. The special value *CURLIB has a fixed meaning: the name of whatever library is considered 'current' in the job.
Because all special values begin with an asterisk, they can be used only in parameters that would normally accept an asterisk as a valid character - that is, the TYPE(*CHAR) only. However, there is a way that you can include special values in almost any data type: with the help of the SPCVAL keyword. Let's illustrate with an example.
Imagine that a command you are creating needs the time of day to schedule a job which is to be submitted for batch processing. Because your shift ends at 6:30 PM, most of the jobs you are likely to submit should default to 6:45 PM. If someone in the second shift submits a job, however, the time should be 1:45 AM. This could be handled by special values to simplify entry and avoid errors. See 6.
Imagine that a command you are creating needs the time of day to schedule a job which is to be submitted for batch processing. Because your shift ends at 6:30 PM, most of the jobs you are likely to submit should default to 6:45 PM. If someone in the second shift submits a job, however, the time should be 1:45 AM. This could be handled by special values to simplify entry and avoid errors. See Figure 6.
Parameter SBMTIME will then accept any valid time, from 000000 to 235959, or special values *END1 and *END2 which are translated by the command processor into 184500 and 014500, respectively. Because the translated value is an acceptable *TIME value, the command will accept *END1 and *END2.
Now let's see what single values are. If one of your parameters is a mixed list or a qualified name, the parameter will comprise at least two values that will be passed to the CPP concatenated into one. A single value, as the name implies, is one value that is translated into two values.
For example, assume your CPP will do some processing on a source physical file. Obviously, the CPP needs to receive the qualified file name from the command object. Because you work so often with source physical file QS36PRC in library PROJECT, however, you decide to plant S36PROJ as the default value for the parameter, as shown in 7.
For example, assume your CPP will do some processing on a source physical file. Obviously, the CPP needs to receive the qualified file name from the command object. Because you work so often with source physical file QS36PRC in library PROJECT, however, you decide to plant S36PROJ as the default value for the parameter, as shown in Figure 7.
Parameter SRCPF is a qualified name because it has TYPE(Q1), which points to two QUAL statements. The default value is S36PROJ. The SNGVAL keyword (single value) indicates that S36PROJ should be translated into 'QS36PRC PROJECT': notice the three imbedded blanks, necessary to make sure that the first ten characters contain the name of the file and the rest contain the name of the library. This is the format in which qualified file names are passed to the CPP.
Establishing Dependencies
As you have already seen, commands give you much in the way of validation of parameters. You have seen how parameters can be restricted to certain values, but what happens if a combination of parameter values is invalid? For example, suppose you create a command to print a report. Parameter PRINTER is used to select the printer to be used and can have the values *SYSTEM, *CURRENT, or *LASER only. Parameter FORMTYPE is used to specify the type of forms and can be either *STD (standard) or *COPY (2-part carbonless). Since laser printers cannot handle multiple-part paper (such as *COPY), it is clear that the command should not accept FORMTYPE(*COPY) if PRINTER(*LASER) has been specified.
This presents a problem, because both PRINTER(*LASER) and FORMTYPE (*COPY) are valid individually; it is the combination that should not be accepted. To solve this problem, you must use the DEP (Dependencies) statement, as shown in 8.
This presents a problem, because both PRINTER(*LASER) and FORMTYPE (*COPY) are valid individually; it is the combination that should not be accepted. To solve this problem, you must use the DEP (Dependencies) statement, as shown in Figure 8.
The CTL keyword states the controlling condition, like an IF statement. The PARM keyword indicates the dependent parameter(s) like a THEN clause. The MSGID keyword, which is optional, indicates the message to be displayed in the case that the dependency is not satisfied.
In our example, this DEP statement means: if &PRINTER is equal to '*LASER', then &FORMTYPE cannot be equal to '*COPY'. If it does, display message XYZ0001. Note that if you use the MSGID keyword, you must name the message file where your messages are located when you create the command using the
CRTCMD command, like this:
CRTCMD CMD(...) PGM(...) +
SRCFILE(...) +
MSGF(message_file_name)
Creating Duplicates of OS/400 Commands
Even well-designed as are most OS/400 commands, there will be commands that do not perform exactly as you might want. For example, the Send Message (SNDMSG) command can be used by anyone to broadcast a message to all active users. All it takes then is a tinkering user discovering this by accident (not too difficult, considering all the help text available) before he or she clutters everyone's message queue with inane small talk.
You could revoke authority to the SNDMSG command, if you are using security level 30 or higher, but that would prevent the tinkering user from sending a message when there is a real necessity - not to mention other problems such as a CL program aborting because it has a SNDMSG command somewhere in its statements.
A better solution is to create your own SNDMSG command, with the restrictions you desire. Simply create a library which you can call DUPCMD (duplicate commands), for example, and place it ahead of QSYS in the system portion of the library list. This is done by changing system value QSYSLIBL using the Change System Value (CHGSYSVAL) command.
In this library you can create your own version of the SNDMSG command which will have only the functions you think are appropriate for the regular user. Your SNDMSG command would call a CL program you also create, which will ultimately run the original SNDMSG command using a statement like this:
QSYS/SNDMSG MSG(...)
Notice that the command name is qualified with QSYS. That will force the system to use the original IBM command.
Another reason for creating duplicates of IBM commands is that you may wish to change the defaults of an IBM command. As you probably have discovered, the Change Command Default (CHG CMDDFT) command can be used to change the existing default value of any parameter in any command. If you use CHGCMDDFT to change IBM commands, however, you will lose all your changes next time you upgrade to the new release of OS/400 or even next time you apply certain PTFs.
Again, the solution is to create a duplicate of IBM's command in your own DUPCMD library which, let me emphasize, must be placed ahead of QSYS in the system portion of the library list.
For example, the Create Physical File (CRTPF) command has a parameter which, in my opinion, has an irritating default: SIZE (number of records) which defaults to 10,000 with a maximum of three increments of 1,000 records each - making an absolute maximum of 13,000 records.
This is ludicrous. Many database files will have many more than 13,000 records. Obviously, then, we should change these defaults. It would have been nice to change the default to *NOMAX, but unfortunately OS/400 issues message CPD6273, which says (quoting the second-level text), "The value *NOMAX is a single value (SNGVAL) for keyword SIZE. A SNGVAL cannot be used as the new default value because no default SNGVAL currently exists." The suggested recovery is not to specify a single value as the new default. Since *NOMAX is not acceptable, we can change the defaults to the highest values that are considered valid, like this:
1. Create a copy of the CRTPF command with the Create Duplicate Object (CRTDUPOBJ) command:
CRTDUPOBJ OBJ(CRTPF) +
FROMLIB(QSYS) +
OBJTYPE(*CMD) +
TOLIB(DUPCMD)
2. Change the defaults of your copy of the command with the CHGCMDDFT command:
CHGCMDDFT CMD(DUPCMD/CRTPF) +
NEWDFT('SIZE(16777215 +
32767 32767)')
Now the initial size of the database file will be 16.7 million records and it can be incremented 32,767 times, each time increasing 32,767 records. The maximum size, then, turns out to be 1,090,453,504, or slightly over one billion. Surely this will be enough.
It is always a good idea to document all of your changes in a CL source program D you do not have to compile it. This program will re-do all the changes you had made on the previous release which, as I said earlier, are otherwise lost when a new release is installed. The CL program should include all the CRTDUPOBJ and CHGCMDDFT commands necessary to bring about the changes in the commands.
When the new release is installed, you should delete all duplicate commands in DUPCMD, then compile and run your CL program. The CL program will then copy the new versions of the commands, and change the default values.
The Bottom Line
You no longer have an excuse for not creating your own commands. Commands are such a powerful tool that I cannot picture no longer using them. They provide a much-needed consistency in the user interface, so that the new end-user can become productive quickly. They also provide syntax and validation checking even before the CPP is involved. Having a maximum of seventy-five parameters, commands can do almost anything and, if named sensibly, they will be as easy to remember as OS/400 commands.
More on Creating Commands
Figure 1A Command STRSYSPRT
Figure 1a: The STRSYSPRT Command STRSYSPRT: CMD PROMPT('Start System Printer') PARM KWD(CMDNAME) TYPE(*CHAR) LEN(10) + CONSTANT('STRSYSPRT')
More on Creating Commands
Figure 1B Command ENDSYSPRT
Figure 1b: The ENDSYSPRT Command ENDSYSPRT: CMD PROMPT('End System Printer') PARM KWD(CMDNAME) TYPE(*CHAR) LEN(10) + CONSTANT('ENDSYSPRT')
More on Creating Commands
Figure 1C CL program SYS001CL
Figure 1c: The SYS001CL Program SYS001CL: PGM PARM(&CMDNAME) DCL VAR(&CMDNAME) TYPE(*CHAR) LEN(10) DCL VAR(&SYSPRT) TYPE(*CHAR) LEN(10) RTVSYSVAL SYSVAL(QPRTDEV) RTNVAR(&SYSPRT) IF COND(&CMDNAME *EQ 'STRSYSPRT') + THEN(STRPRTWTR DEV(&SYSPRT)) ELSE CMD(ENDWTR WTR(&SYSPRT)) ENDPGM
More on Creating Commands
Figure 2A STRSYSPRT command with zero element parm
Figure 2a: The STRSYSPRT Command STRSYSPRT: CMD PROMPT('Start System Printer') PARM KWD(CMDNAME) TYPE(*CHAR) LEN(10) + CONSTANT('STRSYSPRT') PARM KWD(OPTION) TYPE(*ZEROELEM)
More on Creating Commands
Figure 2B ENDSYSPRT command
Figure 2b: The ENDSYSPRT Command ENDSYSPRT: CMD PROMPT('End System Printer') PARM KWD(CMDNAME) TYPE(*CHAR) LEN(10) + CONSTANT('ENDSYSPRT') PARM KWD(OPTION) TYPE(*CHAR) LEN(8) RSTD(*YES) + VALUES(*CNTRLD *IMMED *PAGEEND) + PROMPT('End option')
More on Creating Commands
Figure 2C CL program SYS001CL
Figure 2c: The SYS001CL Program SYS001CL: PGM PARM(&CMDNAME &OPTION) DCL VAR(&CMDNAME) TYPE(*CHAR) LEN(10) DCL VAR(&OPTION) TYPE(*CHAR) LEN(8) DCL VAR(&SYSPRT) TYPE(*CHAR) LEN(10) RTVSYSVAL SYSVAL(QPRTDEV) RTNVAR(&SYSPRT) IF COND(&CMDNAME *EQ 'STRSYSPRT') + THEN(STRPRTWTR DEV(&SYSPRT)) ELSE CMD(ENDWTR WTR(&SYSPRT) OPTION(&OPTION)) ENDPGM
More on Creating Commands
Figure 3 SAVFILE command
Figure 3: The SAVFILE Command SAVFILE: CMD PROMPT('Save a File') PARM KWD(FILE) TYPE(*NAME) LEN(10) MIN(1) + PROMPT('File name') PARM KWD(MEDIA) TYPE(*CHAR) LEN(4) RSTD(*YES) + DFT(*DKT) VALUES(*DKT *TAP) + PROMPT('Type of media') PARM KWD(ENDOPT) TYPE(*CHAR) LEN(7) RSTD(*YES) + DFT(*REWIND) VALUES(*LEAVE *REWIND + *UNLOAD) PMTCTL(PC1) PROMPT('End option') PC1: PMTCTL CTL(MEDIA) COND((*EQ *TAP))
More on Creating Commands
Figure 4 CLCDAYWEK command
Figure 4: The CLCDAYWEK Command CLCDAYWEK: CMD PROMPT('Calculate Day of the Week') PARM KWD(DATE) TYPE(*DEC) LEN(6 0) MIN(1) + PROMPT('Input date') PARM KWD(DOW) TYPE(*CHAR) LEN(3) RTNVAL(*YES) + CHOICE('MON,TUE,WED,THU,...') + PROMPT('Return day of the week')
More on Creating Commands
Figure 5 Sample usage of the CLCDAYWEK command
Figure 5: Sample Usage of the CLCDAYWEK Command DCL VAR(&DAY_WEEK) TYPE(*CHAR) LEN(3) DCL VAR(&SYSDATE) TYPE(*DEC) LEN(6 0) CLCDAYWEK DATE(&SYSDATE) DOW(&DAY_WEEK)
More on Creating Commands
Figure 6 Example of using special values
Figure 6: Example of Using Special Values PARM KWD(SBMTIME) TYPE(*TIME) DFT(*END1) + SPCVAL((*END1 184500) (*END2 014500)) + PROMPT('Time job submitted')
More on Creating Commands
Figure 7 Example of using single values
Figure 7: Example of Using Single Values PARM KWD(SRCPF) TYPE(Q1) DFT(S36PROJ) + SNGVAL((S36PROJ 'QS36PRC PROJECT')) + PROMPT('Source physical file') Q1: QUAL TYPE(*NAME) LEN(10) QUAL TYPE(*NAME) LEN(10) DFT(*LIBL) + SPCVAL((*LIBL)) PROMPT('Library')
More on Creating Commands
Figure 8 Example of using dependencies
Figure 8: Example of Using Dependencies ANYCMD: CMD PROMPT('Any command') PARM KWD(PRINTER) TYPE(*CHAR) LEN(8) RSTD(*YES) + DFT(*CURRENT) VALUES(*CURRENT *SYSTEM + *LASER) PROMPT('Printer to be used') PARM KWD(FORMTYPE) TYPE(*CHAR) LEN(5) RSTD(*YES) + DFT(*STD) VALUES(*STD *COPY) + PROMPT('Type of forms') DEP CTL(&PRINTER *EQ '*LASER') + PARM((&FORMTYPE *NE '*COPY')) MSGID(XYZ0001)
LATEST COMMENTS
MC Press Online