Command choice example

Sometimes, when creating a command, you would like to have a parameter allow dynamic values. The following examples come from a production system I wrote a few months ago.

I needed to allow payroll to track outside labor hours without entering them directly into shop floor. I wrote a simple batch entry system to accomplish this task. One of the restrictions I made to the system was that, the batch name entered must be valid at the time the command prompt screen is shown.

I could have written a validity checking program to perform this task, but it would only return messages such as “The batch ID you entered is incorrect.” I wanted the payroll clerks to see open batch IDs while in the command.

The command choice program saved the day!

Here is how command choice programs work:

  • The program (that you write) can be called in two modes:
    • “text mode” which returns the descriptive text for a parameter
    • “parameter mode” which shows a list of allowed values supplied by your program
  • When you prompt a command, your command choice program is called with two parameters:
    • A 21-position “data structure” with the following components:
    • Positions 1-10 (10 characters) contain the command name calling this program
    • Positions 11-20 (10 characters) contain the keyword name of the parameter
    • Position 21-21 (1 character) is the mode the program should run in:
      • “C” for “text” mode
      • “P” for “parameter” mode
    • Either a 30-position return buffer (for “text” mode) or a 2000-position return buffer (for “parameter” mode) Since these return buffers are passed by reference, your program must ensure it honors the mode; otherwise, it could attempt to write outside the referenced region and cause a machine error.
  • Depending on the mode called, you return static text or a parameter list.

There are some limitations to command choice programs:

  • Even though you can prompt the list, you are not required to enter any of the values shown. (You can enter anything you want in the field.) The use of a validity checking program ensures the command processing program (CPP) receives valid parameters. Do not rely on the command choice program to ensure valid parameter values!
  • You cannot select from the displayed list; you must type the value in the parameter blank. Again, validity checking programs help prevent typing errors from the CPP.
  • The list is scrollable, but without positioning capabilities.
  • Depending on the amount processing required in the choice program, system performance can be affected. A small delay may be present while the choice program generates the list for the command prompter.

On my production system, all these objects reside in library PROD_MOD. Change this to a suitable name on your system.

Temporary labor batch control file

This is the DDS for the batch control file used in my temporary labor system:

                                           UNIQUE
               R TLABCTLR
                 BATCHID       10A         COLHDG('BATCH' 'ID')
                 LABORDATE       L         COLHDG('LABOR' 'DATE')
                 POSTREADY      1A         COLHDG('READY' 'TO POST')
                 INUSE          1A         COLHDG('BATCH' 'IN USE')
                 USERC         10A         COLHDG('USER' 'CREATED')
                 DATEC           L         COLHDG('DATE' 'CREATED')
                 TIMEC           T         COLHDG('TIME' 'CREATED')
               K BATCHID


The sample command choice program will read the BATCHID fields from the file and present them to the command prompter. Since the file is unique and keyed on the batch ID, it will be shown in alpha order.

Command definition

Here is the command defintion for maintaining batches:


/* The command processing program is TLABMTNBE. */
/* Compile with VLDCKR(TLABZVC2) */

cmd prompt('Temp labor maintain batch')
parm kwd(batchid) type(*char) len(10) min(1) prompt('Batch ID (F4=Batch list)') +
   choice(*pgm) choicepgm(prod_mod/tlabzch)
parm kwd(entrymode) type(*char) len(10) rstd(*yes) dft(*enter) +
   values(*enter *maintain *errors) prompt('Data entry mode')

Command choice program

This performs the work for parameter BATCHID in the command definition. The comments are not present in the downloaded program.


      *-- The file has the USROPN option since we do not need it to return
      *-- text, only the parameters. Odds are this program is called much
      *-- more often with the 'C' code than the 'P' code.
     ftlabctlp  if   e           k disk    usropn

      *-- As mentioned above, the 21-position data field is actually a
      *-- data structure containing the command name calling this program,
      *-- the keyword of the parameter needing this information, and the
      *-- mode which this program should run.
     d inparms         ds            21
     d  incommand                    10a
     d  inkeyword                    10a
     d  intype                        1a

      *-- The return data is either 30 bytes or 2000 bytes, and is
      *-- normally passed by reference (pointer). As long as you obey the
      *-- requested mode, this works without problems. (That is, do NOT
      *-- return 31 bytes if the program wants choice text. Bad things
      *-- happen.
     d outdata         s           2000

      *-- Defines the mode in which this program should operate.
      *-- 'C' mode only returns choice text to the command prompter (see
      *--     TEXTDS and RETURNTEXT below).
      *-- 'P' mode returns a list of values to be shown by the command
      *--     prompter.
     d CHOICETEXT      c                   'C'
     d CHOICEPARMS     c                   'P'

      *-- Maximum number of entries to return to the command prompter.
      *-- Because each entry has two bytes 'wasted' for its length,
      *-- we cannot store 200 items in the list. However,
      *-- ((150 * 10) + (150 * 2)) = (1500 + 300) = 1800 bytes.
      *--
      *-- Since the command prompter reads the list in the same way
      *-- you would process a list API, it can handle varying sizes for
      *-- each field. However, I'm lazy, and know the maximum size of
      *-- any batch ID is 10 bytes.
      *--  
      *-- The return value can hold a maximum number of 166 fixed-
      *-- size entries of 10 bytes each:
      *-- ((166 * 10) + (166 * 2)) = (1660 + 332) = 1992 + 2 bytes
      *-- for number of entries = 1994 bytes used of 2000.
      *--
      *-- A good exercise for the reader would be to adapt this
      *-- program to use variable lengths for each entry rather than
      *-- 10. (Hint: the MAXENTRIES constant goes away when you do this.)
     d MAXENTRIES      c                   150
     d RETURNTEXT      c                   'Open, unposted batches'

     d start           s              5  0 inz

     d textds          s             30a   inz

      *-- If returning parameters, the return variable has a required
      *-- layout:
      *-- Positions 1-2      (2 bytes)      Number of entries following
      *-- Positions 3-2000   (1998 bytes)   List of values with the
      *--                                   following format:
      *-- +-> (2 bytes)    length of value following
      *-- |   (variable)   value
      *-- +--
     d parmds          ds          2000
     d  numentries                    5i 0 inz
     d  parmdata                   1998a   inz

      *-- This is each 'entry' referred to by NUMENTRIES. It is composed
      *-- of two bytes denoting length, and variable length entry data.
      *-- Reading 10-byte fields from a file made it a no-brainer to
      *-- make all entries the same size. This is somewhat wasteful, but
      *-- I doubt payroll will have 150 open batches to view. This is a
      *-- conscious design decision.
     d elementds       ds            12
     d  elemlen                       5i 0 inz
     d  element                      10a   inz

     c     *entry        plist
     c                   parm                    inparms
     c                   parm                    outdata

     c                   select

      *-- Return text prompt only.
      *-- To be safer (and as an exercise for the reader), we should
      *-- ensure we only return the first 30 characters of the text
      *-- prompt.
     c                   when      intype = CHOICETEXT
     c                   eval      textds = RETURNTEXT
     c                   eval      outdata = textds

      *-- Return a parameter list.
     c                   when      intype = CHOICEPARMS

      *-- NOTE: It is legal to return 0 entries to the command prompter.
      *--       This results in a screen with the parameter prompt only,
      *--       with no value list beneath.
     c                   eval      numentries = 0
     c                   eval      parmdata = *blanks
     c                   eval      start = 1

      *-- We only open and close the batch control file when we need to
      *-- build a parameter list. This should happen quite rarely.
     c                   open      tlabctlp
     c     *start        setll     tlabctlp
     c                   read      tlabctlp

      *-- Protect return variable from overflow by limiting to MAXENTRIES
      *-- value. Only the first MAXENTRIES will be returned to the command
      *-- prompter.
     c                   dow       not %eof(tlabctlp)
     c                             and numentries <= MAXENTRIES
     c                   eval      elemlen = %size(element)
     c                   eval      element = batchid
     c                   eval      numentries = numentries + 1

      *-- Inline replacement in the return variable.
     c                   eval      %subst(parmdata:start:%size(elementds)) =
     c                             elementds
     c                   eval      start = start + %size(elementds)
     c                   read      tlabctlp
     c                   enddo
     c                   close     tlabctlp
     c                   eval      outdata = parmds
     c                   endsl

      *-- NOTE: Variable OUTDATA (the return value) should be populated
      *--       when we get here or 'the results are unpredictible'.
     c                   eval      *inlr = *on
     c                   return

Validity checking program

Finally, here is the validity checking program referenced in the TLABMTNB command.


pgm parm(&batch &mode)

dcl var(&batch) type(*char) len(10)
dcl var(&mode) type(*char) len(10)

dcl var(&found) type(*lgl) value('0')
dcl var(&msg) type(*char) len(80)
dclf file(tlabctlp)

loop:
rcvf
monmsg msgid(cpf0864) exec(goto loopout)

if cond(&batchid = &batch) then(chgvar var(&found) value('1'))
goto cmdlbl(loop)

loopout:
if cond(&found = '0') then(do)
   chgvar var(&msg) value('Batch ID' *bcat &batch *tcat ' was not found. +
        Press F4 to see available batches.')
   sndpgmmsg msgid(cpd0006) msgf(qcpfmsg) msgdta('0000' *cat &msg) +
        msgtype(*diag)
   sndpgmmsg msgid(cpf0002) msgf(qcpfmsg) msgtype(*escape)
enddo

endpgm

Follow-ups

Asked

I am trying to achieve something similar - except I want to actually set the default value for the parameter dynamically. Below is something I e-mailed to another site but never received any reply. Any ideas?

I have a particular need to create my own command to copy data from one file to another. It would effectively be a cut down version of cpyf (in fact the cpp has little more than a cpyf command in it) with only the following parameters - from library & file, to library & file and number of records to copy. So far so good. But–I want it to work as follows. Initially, I would enter the library and file names and press Enter. A program would then retrieve the number of records in the from file and default this as the parameter value for number of records to copy (by default I would copy all records), although I would be able to change the value. Pressing Enter again would perform the copy. I have looked at all the different programs that you can associate with a command, prompt overrides, validity checkers etc. and can’t find a way to do this. Is it possible?

Hanna answered

I’ll ask this first off, then make a suggestion. Why not use *ALL for the number of records to copy. Then your CPP could use *START *END as the range (if *ALL was specified).

OK. One way you could achieve this is as follows:

  • Command 1 prompts for the from-file and to-file.
  • Command 1’s CPP is invoked. It retrieves the number of records (assuming the *FIRST member of the file).
  • Compile command 2 into the QTEMP library. Comamnd 2 contains parameters for the from- and to-file, and also the number of records extracted in CPP1. Use CHGCMDDFT on QTEMP/command2 to set the default number of records. Command 2’s CPP is your CPP to perform the copy.
  • Prompt QTEMP/command2 to let the user choose the number of records.

A sample program (quick) might look like this:

    pgm parm(&fromfile &tofile)
    dcl var(&fromfile) type(*char) len(10)
    dcl var(&tofile) type(*char) len(10)
    dcl var(&nbrcurrcd) type(*dec) len(10 0)
    rtvmbrd file(&fromfile) nbrcurrcd(&nbrcurrcd)
    crtcmd cmd(qtemp/command2) pgm(cpp2)
    cmdcmddft cmd(qtemp/command2) newdft('nbrrcds(&nbrcurrcd)')
    ? qtemp/command2
    endpgm


You may need to play with the prompting, but this would allow you to actually show the number of records to be copied.

Second follow-up by Hanna

OK. Here is a way to implement the previous request to show the number of records to be copied in a command.

You must take the responsibility to ensure the code works as you intend before deploying it in a production environment. Although I believe the programs posted in this message work as shown, they may be incomplete for error checking and parameter checking. They are intended as examples, not fully finished products.

Command1 is the user’s command to prompt for the file to be copied, and the destination. Its command processing program is command1c.

cmd prompt('Command 1')
parm kwd(fromfile) type(*name) len(10) prompt('From file')
parm kwd(tofile) type(*name) len(10) prompt('To file')


Command1’s processing program command1c determines the number of records in the from-file, and sets up command 2 for processing.

When we retrieved the number of records for the from-file, we received them in a numeric variable. In order to use the same number in the chgcmddft command string, we must convert the number to a character string to use (the first chgvar line).

You cannot execute chgcmddft using CL substitution variables as I wrote in my previous post; you get a CPD0103 error. To overcome this, we will create the command to be executed as a dynamic string, and pass it to the command execution program QCMDEXC.

NOTE: To compile command1c, you must first compile command2 into the QTEMP library. Otherwise, the compile fails because the the prompt line (third line from the bottom) is using the qualified library QTEMP to find command2.

pgm parm(&fromfile &tofile)
dcl var(&fromfile) type(*char) len(10)
dcl var(&tofile) type(*char) len(10)
dcl var(&nbrcurrcd) type(*dec) len(10 0)
dcl var(&nbrcurrcdc) type(*char) len(10)
dcl var(&command) type(*char) len(200)
dcl var(&commandlen) type(*dec) len(15 5) value(200)
dcl var(&quote) type(*char) len(1) value('''')
rtvmbrd file(&fromfile) nbrcurrcd(&nbrcurrcd)
chgvar var(&nbrcurrcdc) value(&nbrcurrcd)
crtcmd cmd(qtemp/command2) pgm(command2c) srcfile(qcmdsrc) srcmbr(command2)
chgvar var(&command) value('chgcmddft cmd(qtemp/command2) newdft(' *cat +
   &quote *cat 'nbrrcd(' *cat &nbrcurrcdc *cat ')' *cat &quote *cat ')')
call qcmdexc parm(&command &commandlen)
? qtemp/command2 fromfile(&fromfile) tofile(&tofile)
monmsg msgid(cpf0000)
endpgm


Command2 has the same file names as was specified for command1, and also has the maximum number of records to be copied as the default in the “Records to copy” parameter. Note that the number of records is the default value, and can be overriden by the user to 0 or more than the default. The command processing program command2c should check these boundary conditions before copying the file.

cmd prompt('Command 2')
parm kwd(fromfile) type(*name) len(10) prompt('From file') min(1)
parm kwd(tofile) type(*name) len(10) prompt('To file') min(1)
parm kwd(nbrrcd) type(*dec) len(10 0) prompt('Records to copy') dft(1)
pgm parm(&fromfile &tofile &nbrrcd)
dcl var(&fromfile) type(*char) len(10)
dcl var(&tofile) type(*char) len(10)
dcl var(&nbrrcd) type(*dec) len(10 0)
dcl var(&nbrcurrcd) type(*dec) len(10 0)
rtvmbrd file(&fromfile) nbrcurrcd(&nbrcurrcd)
if cond(&nbrrcd *le 0) then(goto cmdlbl(endpgm))
if cond(&nbrrcd *gt &nbrcurrcd) then(chgvar var(&nbrrcd) value(&nbrcurrcd))
cpyf fromfile(&fromfile) tofile(&tofile) crtfile(*yes) fromrcd(1) torcd(&nbrrcd)
endpgm:
endpgm


Revision 1.0, 19 Jul 2002, Hanna Goodbar.