
      PROGRAMMING  NOTES                   A WindoWatch Series


                 WINDOWS  ASPECT: A Scripting Language

           A Tutorial - Part Four for Procomm for Windows v.2
               GHOST BBS v.3.20 (C) 1995 by Gregg Hommel


  We left you and George with....

  integer holding = 1

  proc main
     when target 0 "?" call get_prompt
     while holding
     endwhile
  endproc

  Have you figured this out yet? If not, don't worry as it wasn't really a
  fair test.  I spent months working on the code that leads to and was
  primarily based upon one observation.

  Remember last column, and the prompts that we had our little script
  working with? Do you perhaps recall one thing common to all of those
  prompt examples?  After a month of watching my log-ons carefully, I found
  myself looking for an easier way to handle them and to get around the
  limit of three WHEN TARGET commands allowed in Wasp 1.0.  I did notice
  something common in my log-ons, at least, with the two types of software I
  use most frequently, PCBoard and WildCat.

  Care to guess what it is?

  Almost every prompt on these two types of systems contain one common
  denominator which turn out to be a "?", appearing most often at the end of
  the prompt. To make matters even easier,  I very rarely found a "?" being
  used during the log-on, which was NOT part of a prompt.  It might
  occasionally appear in a bulletin during log-on which can be easily taken
  care of.  In any case, how does this help us?

  Putting two and two together, and coming up with five, I decided that
  perhaps watching for that "?", and then figuring out which prompt was
  issued would solve my problem of not enough WHEN TARGET commands.  I
  re-discovered the  multitude of different, possible prompts on these two
  systems. It certainly had to be better than trying to watch for every
  possible prompt during a log-on.

  Now that I had my suggestive five, I really needed ten!  How was I to
  determine which prompt was sent, when I ran across that "?"...............?

  Fortunately, Wasp has an answer to that problem known as the TERMGETS
  command. The basic definition is  TERMGETS reads the TERMinal screen,  GETs
  a String from it, and then puts that string into a string variable. All I
  had to do was to figure out how to locate the prompt on the screen, use
  TERMGETS to read it into a variable, and then check to see if any keywords
  from different prompts were in that string variable.

  Fortunately, PCBoard and WildCat proved to have another common feature to
  their prompts during log in.  When the prompt is placed on the terminal
  screen, both PCBoard and WildCat (and likely a lot of other software) wait
  for the response to the prompt with the cursor positioned at the end of
  the prompt text,on the same line as the prompt. This made it easy!  Just
  tell TERMGETS to get the string of text on the Terminal located in the
  current row and from the first column to the current column. Wasp made
  that even easier by including $ROW and $COLUMN system variables, which
  contain the current terminal row and column data. Since the display from
  the BBS is stopped while waiting for a response to the prompt, the code
  line...

   termgets $ROW 0 prompt_str $COL

  reads the text on the terminal into the variable, prompt_str, in the line
  the cursor is currently on, from column index 0 to the current column
  index of the cursor. In other words, it gets the complete line of text
  which makes up the prompt we are trying to handle, exactly as it is
  displayed on the terminal.

  All we have to do now, is to determine which, of many possible prompts, is
  currently in our string variable, prompt_str. For that trick, we will turn
  to the Wasp command, STRFIND, which attempts to find a defined string
  within a target string.

  Before we go on,  some of you are probably wondering why we don't use the
  Wasp STRCMP command, instead, to compare the contents of prompt_str to the
  string we are looking for. The reasoning behind not doing so is fairly
  simple.

  STRCMP requires a complete and exact match between the two strings being
  tested. If you use STRICMP, case is not an issue, but the match does not
  occur unless it is complete and identical other than case.

  As example, let's say that our prompt_str contains the text "What is your
  password?". To use even STRICMP, we would have to test for the string"what
  is your password?". No problem, you say.  Let's suppose the next day, the
  sysop of the BBS decides to personalize things, and use your first name in
  the prompt which is possible with most BBS software.  Now the contents of
  the prompt_str variable is "What is your password, GREGG?" and our STRICMP
  test fails.  The resulting failure because the contents of prompt_str no
  longer matches completely and identically with our test string, our script
  doesn't answer the prompt.  After three minutes, the BBS times us out and
  hangs up on us.

  Now, let's look at STRFIND. For this to result in a match, the match does
  not have to be exact over the entire length of the string.  It simply has
  to be exact over the entire length of the test string and ANY PORTION OF
  THE TARGET STRING. Therefore, if we were to use STRFIND to test for the
  keyword "password" in the prompt above, it would not matter one whit to us
  if the sysop changes the prompt to include our first name. Since the
  keyword "password" remains somewhere within the new prompt, STRFIND will
  locate it, and result in a match. That is all we care about for now! Well
  not all but at least enough for this column.

  We now have the two basic conceptss necessary to complete at least a
  rudimentary log-on script for a generic system. Our original code (way
  back at the beginning of this column) waited for the "?" in a prompt, and
  when it spotted it, called a procedure "get_prompt". We can now start that
  procedure off, like this....

  proc get_prompt
     string prompt_str
     termgets $ROW 0 prompt_str $COL
     if strfind prompt_str "name"
        transmit $USERID
        transmit "^M"
     elseif strfind prompt_str "password"
       transmit $PASSWORD
       transmit "^M"
     elseif strfind prompt_str "Read it"
       transmit "N^M"
     elseif   ...more code here.....

    endif
  endproc

  We're not done yet, though. In the main procedure from last column (see
  the top of this one), we had a little WHILE... ENDWHILE that needs some
  explanation. Basically, it consisted of this code...

   while holding
   endwhile

  with the value of "holding" having been defined by the script as being 1.
  I suspect that most of you will recognize that this code would put the
  script into an endless loop from which it would never exit.  Kind of
  silly, except that we won't let it happen quite that way. The code is just
  what the name of the variable used within it, says it is... a holding
  pattern. It is designed to keep the script from exiting until  I want it
  to. That's why I used a GLOBAL variable for holding.  As a global, I can
  change the value of holding from elsewhere in the script, and thus cause
  the script to be exited when I want it to stop running. Where?  Well, for
  now, let's tell the script to stop running when it reaches the PCBoard
  Main Board Command prompt. How? That one is easy. Using the code above for
  our sub-procedure, we'll write it out here again.

  proc get_prompt
    string prompt_str
    termgets $ROW 0 prompt_str $COL
    if strfind prompt_str "name"
      transmit $USERID
      transmit "^M"
    elseif strfind prompt_str "password"
      transmit $PASSWORD
      transmit "^M"
    elseif strfind prompt_str "Read it"
      transmit "N^M"
    elseif strfind prompt_str "Command"
      holding = 0
    endif
  endproc

  Setting the value of holding to 0 when the prompt includes the keyword
  Command, results in the WHILE... ENDWHILE construct no longer being TRUE,
  and the script exits.  As you can see, our script will take us as far as
  the main prompt on a PCBoard system, and then exit.

  In order to accommodate the multitude of possible prompts on a system
  during a log-on, we must add additional conditionals in the format of
  'elseif strfind prompt_str "text"', with code to handle the response that
  we wish to make to that prompt. As example, many PCBoard systems offer
  multiple languages, and choice of graphics or no graphics at log-on. Using
  the standard prompts for these, when we add the following to the above
  code, our script could handle those also.

   elseif strfind prompt_str "Enter)=yes"
      transmit "N^M"
   elseif strfind prompt_str "=no change"
      transmit "^M"

  We assume that we want the default language for the system, and that we
  don't want ANSI graphics.

  Let's now take a look at our entire script, as it exists so far.

  integer holding = 1

  proc main
     when target 0 "?" call get_prompt
     while holding
     endwhile
  endproc

  proc get_prompt
     string prompt_str
     termgets $ROW 0 prompt_str $COL
     if strfind prompt_str "name"
        transmit $USERID
        transmit "^M"
     elseif strfind prompt_str "password"
        transmit $PASSWORD
        transmit "^M"
     elseif strfind prompt_str "Read it"
        transmit "N^M"
     elseif strfind prompt_str "Enter)=yes"
        transmit "N^M"
     elseif strfind prompt_str "=no change"
        transmit "^M"
     elseif strfind prompt_str "Command"
        holding = 0
     endif
  endproc

  However, once we have added this code there is an element in our procedure
  which I dislike. Two of our conditionals send precisely the same response,
  i.e. "N^M".  I personally dislike using extra code to do the same thing.
  With most BBS, it is quite possible that, during our log-on, we will
  receive several prompts, many of which will receive the same response.
  This means that our code begins getting heavy with repeats of the TRANSMIT
  looking for a single response to handle all of the prompts which might
  require that response from us.

  There is a way to eliminate, or to minimize the number of times that we
  duplicate the same response code in our script.  This is a good time to
  discuss the use of a function under Wasp, since that is what we will use.In
  Wasp, it is possible to put multiple conditions in a single line, using the
  logical "&&" or "||" operators.  However, it is rarely possible to put
  multiples of the SAME condition in a single line of code, such as our
  STRFIND commands. This is where a Wasp function can be useful, as it is
  quite possible to test multiple returned values from the same function on a
  single line.

  So, what is a function in Wasp?  Basically, it is a special kind of
  sub-procedure, which returns some kind of value to the calling procedure,
  as a result of whatever code is in that function. The STRFIND command
  actually returns an integer value for the Wasp system variable, SUCCESS,
  which is what our conditionals above tested. This kind of command (i.e.
  one which cannot be used multiple times in a single conditional, but which
  returns some value as a result of the command) is ideal for use in a
  function, such as this one.

  func CheckPrompt : integer
    param string dummy
    strfind prompt_str dummy
    return success
  endfunc

  Notice the differences between a function and a procedure? First, it is
  declared as a function by the FUNC name instead of PROC.  Next, in the
  opening line, we must not only define the name of the function, but also
  define what type of value that function will be returning to our calling
  procedure, for this case, an integer. Third, the code has to include at
  least one return statement (unlike a procedure, which doesn't require one),
  to tell the function which value is to be returned to the calling procedure.
  And last, the function ends using an ENDFUNC statement, rather than an
  ENDPROC.

  To use this function in our script, we need to do one other thing.  The
  variable prompt_str, which we have previously declared as a local string
  variable in the procedure get_prompt, must instead,  be declared as a
  global string variable at the beginning of our script. This results in the
  following script.

  integer holding = 1
  string prompt_str

  proc main
   when target 0 "?" call get_prompt
   while holding
   endwhile
  endproc

  func CheckPrompt : integer
    param string dummy
    strfind prompt_str dummy
    return success
  endfunc

  proc get_prompt
    termgets $ROW 0 prompt_str $COL
    if CheckPrompt("name")
       transmit $USERID
       transmit "^M"
    elseif CheckPrompt("password")
       transmit $PASSWORD
       transmit "^M"
    elseif CheckPrompt("Read it") || CheckPrompt("Enter)=yes")
       transmit "N^M"
    elseif CheckPrompt("=no change")
       transmit "^M"
    elseif CheckPrompt("Command")
       holding = 0
    endif
    prompt_str = $NULLSTR

  endproc

  Notice we now have only one conditional which results in the use of
  'transmit "N^M" ', as the conditional now contains two items to be tested
  using our function. If either one of the two function calls results in a 1
  being returned (i.e. if either keyword is found in the prompt), then the
  conditional is met and the response sent. If neither function call returns
  a 1, then the response is not sent, and we continue looking for the right
  keyword.

  I know that the above script does not truly show the advantage of using a
  function, so let's look at a situation which shows it off a little better.
  Suppose we want to make our script a little more generic, so that we can
  use the thing with several PCBoard systems which we call (we'll get to
  WildCat systems in the next column). I have been on more than a few
  PCBoard systems, and the standard prompt is not always the one being used.

  As an example, for the graphics prompt, where we have used "Enter)=yes" as
  our keyword string, I have also seen "Enter = Yes", "Enter=(Y)" and
  "Enter)=default" used in the prompt.  If we want to make our script generic,
  we would have to make sure that we handled all of these possibilities, and
  that all of them get the same response.  To cover them all, this is where we
  use our function and simply add the "||" (OR) operator, and another
  CheckPrompt for each of these text strings.

  Should we log-on to a new system, and discover a variation of the keywords
  for our graphics prompt test, all we have to do is add another text of
  CheckPrompt for that different graphics prompt. By adding another text to
  our existing conditional recompile, and our script will handle it, instead
  of adding a whole new conditional with duplicated response code.

  I think that George has enough to mull over for this month, so we'll stop
  here until next time, when we will look at WildCat systems, and how they
  differ from PCBoard. We'll also look at INI files, and using them to
  control how a script functions.

  If you have any questions or comments regarding anything so far, I can be
  found in the Windows and Procomm conferences of RIME, ILink, NANet, and
  EchoNet. My FIDO net mail address is (1:229/15), my Internet address is
  gregg.hommel@canrem.com, and my Compuserve ID is 72537,552.

  Gregg Hommel is an active communications consultant having recently done
  contract work for organizations like Delrina. He is well known on the
  various nets here and in Canada and serves as Co-Host of the Rime Windows
  conference. Gregg serves as a member of the WindoWatch editorial board as
  well as writing these columns.


                                      ww



