{.PL66}
{ $B-,C-,I-,R-,V-,U-,K- }
program climb; { Version V01.03 06/10/84 13:37 }

{ This routine allows you to climb directory trees using TURBO Pascal.    }
{                                                                         }
{Syntax: CLIMB (-|?)UP    (d:)    Examples:   CLIMB UP A:                 }
{                   DOWN                      CLIMB -DOWN C:              }
{                   LEFT                      CLIMB LEFT                  }
{                   RIGHT                     CLIMB -RIGHT                }
{                                             CLIMB ?L                    }
{                                             CLIMB -?RI                  }
{                                                                         }
{ All paramters may be in upper or lower case and abbreviated.  If the    }
{ "do quietly" hyphen or the "query" question mark is specified, there    }
{ must be no interleaving space between it and the subcommand.            }
{                                                                         }
{ When specified, the "do quietly" indicates that messages should be      }
{ suppressed.  This is useful in .BATs.                                   }
{                                                                         }
{ When the "query" question mark is specified, instead of moving in the   }
{ indicated direction, the command simply indicates what action would be  }
{ taken.  It may be useful in .BAT programs to see if you can descend in  }
{ a given direction, but not have the path indicated.  CLIMB allows you to}
{ specify both "do quietly" and "query" in either order as long as they   }
{ are the first two characters.  It is also possible to specify -- or ??  }
{ in which case the second - or ? is ignored.                             }
{                                                                         }
{ If a drive is not specified, the default drive is assumed.              }
{                                                                         }
{Up is obvious.  Down is the next lower path.  If there are multiple      }
{lower paths, the first alphabetically is choosen.  Left goes up a path,  }
{choses the next lower (alphabetically) path and descends to that.  Right }
{is the opposite of right: it goes up a path, choses the next higher path }
{alphabetically, and descends to that.                                    }
{                                                                         }
{If you can't climb in any direction, a nonzero return code is generated. }
{                                                                         }
{ Return code   Meaning                                                   }
{ 0..........   Successful climb                                          }                                                                        }
{ 1-18 ......   DOS error codes, see DOS Manual page D-14                 }
{ 20.........   No paths in that direction.                               }
{ 255........   Invalid parameter                                         }
{                                                                         }
{Note that messages generated are written to the DOS standard output      }
{device so you can pipe the messages to another file or device.           }
{                                                                         }
{ The following is a batch file that illustrates how CLIMB will allow you }
{ to examine every directory on a disk. The name of this file is          }
{ typically called TOUR.BAT in that is allows you to tour the directories }
{ on a disk.                                                              }
{                                                                         }
{echo off                                                                 }
{rem  This file "tours" all of the subdirectories using CLIMB             }
{chdir \                                                                  }
{:cmd_down                                                                }
{REM ********** Insert commands below*******************                  }
{REM ... First, display the directory ...                                 }
{cd                                                                       }
{REM ... Next, use your favorite directory command to list the contents   }
{REM ... This is a good place to use the public domain SDIR command.      }
{dir                                                                      }
{REM ********** Insert commands above*******************                  }
{climb -down                                                              }
{if errorlevel 1 goto right                                               }
{goto cmd_down                                                            }
{:right                                                                   }
{climb -right                                                             }
{if errorlevel 1 goto up                                                  }
{goto cmd_down                                                            }
{:up                                                                      }
{climb -up                                                                }
{if errorlevel 1 goto exit                                                }
{goto right                                                               }
{exit:                                                                    }
{Echo Tour is complete.                                                   }
{ .... End of TOUR.BAT ....                                               }
{                                                                         }
{ If you are compiling this program using TURBO Pascal Version 2.0,       }
{ you can eliminate the annoying clear screen everytime this program      }
{ runs.  This procedure can be applied to all Turbo Pascal programs for   }
{ which you wish to eliminate the clear screen during initialization,     }
{ provided they have been compiled using version 2.0 of the Turbo Pascal  }
{ compiler...                                                             }
{                                                                         }
{ DEBUG CLIMB.COM       <- Or any or compiled Turbo Pascal Program        }
{ -U 2FC                <- Make sure this is the right version            }
{ ssss:02FC INT 10      <- ssss will vary, first instruction listed       }
{                          must be the INT 10.  Others will follow.       }
{ -A 2FC                                                                  }
{ ssss:02FC NOP         <- type in NOP                                    }
{ ssss:02FD NOP         <- twice                                          }
{ ssss:02FE             <- press enter                                    }
{ -W                    <- this replaces the program                      }
{ Writing hhhh bytes                                                      }
{ -Q                    <- quit debug                                     }
{                                                                         }
{;                                                                        }
{; Copyright (c) 1984 Thomas J. Foth                                      }
{; All Rights Reserved                                                    }
{;                                                                        }
{; Permission is granted to freely distribute this code, but not for      }
{; profit and provided that the following address and disclaimer are      }
{; included.                                                              }
{;                                                                         }
{; Portions of this program may be used freely, in other works, provided   }
{; that credit to the author and this work appear with the portions used.  }
{;                                                                         }
{; The author's address:                                                   }
{;                                                                         }
{; Thomas J. Foth                                                          }
{; 260 Sunset Ave.                                                         }
{; Fairfield, CT 06880                                                     }
{; (203) 334-6401                                                          }
{;                                                                         }
{; Disclaimer:                                                             }
{;                                                                         }
{; This program is provided "as-is" without warranty of any kind, either   }
{; expressed or implied, including, but not limited to the implied         }
{; warranties of merchantability and fitness for a particular purpose.     }
{;                                                                         }
const
     readonly = $01;   { DOS 2.0 Attribute byte definitions }
     hidden   = $02;
     system   = $03;
     vollabel = $08;
     directory= $10;
     archive  = $20;
     mkdir    = $39;
     rmdir    = $3A;
     chdir    = $3B;
     del      = $41;
     chmod    = $43;
     find_1st = $4E;
     find_nxt = $4F;

type pathchar   = array[1..64] of char; { Used to communicate w/DOS }
     asciiz     = array[1..255] of char;{ This array is an ASCIIZ string }
     pathstring = string[255];          { For procedural calls }
     anystring  = string[255];

     DTAptr     = ^DTA;

     DTA    = record                    { The Disk Transfer Area mapped out }
               dos_usage: array[1..21] of char; { DOS internal usage        }
               attribute: byte;         { File attribute, see constants     }
               ftime    : integer;      { See page C-5 for explanation      }
               fdate    : integer;      { Ditto                             }
               fsize_lo : integer;      { See page C-6 for explanation      }
               fsize_hi : integer;      { Ditto                             }
               fname    : asciiz;       { filename.ext terminate by null byte}
              end;
var
   i, rc           : integer;

   lookup_pattern,
   current_path,
   new_path,
   low_path,
   upper_path,
   this_branch     : pathstring;

   parm            : string[11];
   parm1_drive     : char absolute CSeg:$5C;
   cparm1          : array[1..11] of char absolute CSeg:$5D;
   drive           : byte absolute CSeg:$6C;
   cparm2          : array[1..11] of char absolute CSeg:$6D;

   quietly,
   query           : boolean;

   current_DTA     : DTAptr;

function asz2str(to_be_fixed: asciiz): pathstring;
var
   i               : integer;
   tmp_str         : string[255];
begin;
 i:=1;
 tmp_str:='';
 while (to_be_fixed[i] <> chr(0)) and
       (i<=255) do
  begin;
   tmp_str := tmp_str+to_be_fixed[i];
   i:=i+1;
  end;
 asz2str := tmp_str;
end;

procedure get_current_path(drive: byte;
                           var the_path: pathstring;
                           var rc:integer);

{ Returns the current path in the form \path1\path2... for drive in
  the path.  If drive = 0, use current drive.  rc is dos return code. }
var
   regs            : record
                      ax,bx,cx,dx,bp,si,di,ds,es,flags: integer;
                     end;
   temp_chars      : asciiz;
   i               : integer;

begin
 regs.dx:=drive;
 regs.ds:=seg(temp_chars);
 regs.si:=ofs(temp_chars);
 regs.ax:=$4700;              { Get the current path }
 MsDos(regs);

 regs.flags := regs.flags and $0001;
 if  regs.flags = 1 then
 rc := regs.ax
 else
      begin;
       the_path:='\'+asz2str(temp_chars);
       rc := 0;
       for i:=1 to length(the_path) do
        if ord(copy(the_path,i,1)) > $60 then
         the_path:=copy(the_path,1,i-1)+
                   Upcase(copy(the_path,i,1))+
                   copy(the_path,i+1,255);
      end;
end;
function trunc_path(the_path: pathstring): pathstring;
{ This truncates the_path to an "upper" level path }

Var i              : integer;

begin;
   i:=length(the_path);
   while (i > 0) and (copy(the_path,i,1) <> '\') do i:=i-1;
   i := i - 1;
   if i <> 0 then
    trunc_path := copy(the_path,1,i)
    else trunc_path := '\';
end;
function this_path(the_path: pathstring): pathstring;
{ This truncates the_path to the current level path }

Var
   i               : integer;

begin;
   i:=length(the_path);
   while (i > 0) and (copy(the_path,i,1) <> '\') do i:=i-1;
    this_path := copy(the_path,i+1,255)
end;

procedure fscall(func_code: integer;
                 drive: byte;
                 the_path: pathstring;
                 mode: integer;
                 var rc:integer);

{ Perform a DOS2.0 file system call.  the_path must be in the
  form \path1\path2... If drive is 0, the current drive is specified.
  Specify drive=0 also allows the_path to take the form d:\path1\path2...}
var
   regs            : record
                      ax,bx,cx,dx,bp,si,di,ds,es,flags: integer;
                     end;
   full_path       : pathstring;

begin
 if drive = 0
  then full_path := concat(the_path,chr(0))
  else full_path := concat(CHR(64+drive),':',the_path,chr(0));
 regs.ds:=seg(full_path);
 regs.dx:=ofs(full_path)+1;
 regs.cx:=mode;
 regs.ax:=swap(func_code);
 MsDos(regs);


 regs.flags := regs.flags and $0001;
 if  regs.flags = 1 then
  rc := regs.ax
 else rc := 0;
end;

procedure stwrite(out_string:anystring);

{
Allows you to write a string to the standard output device, thus
allowing piping to take place
}
var
   regs            : record
                      ax,bx,cx,dx,bp,si,di,ds,es,flags: integer;
                     end;

begin
 regs.bx:=1;                  { Standard output device handle }
 regs.cx:=length(out_string);
 regs.ds:=seg(out_string);
 regs.dx:=ofs(out_string)+1;
 regs.ax:=$4000;
 MsDos(regs);

end;

procedure stwriteln(out_string:anystring);

{
Allows you to write a string to the standard output device, thus
allowing piping to take place
}
begin

 stwrite(out_string+chr(13)+chr(10));

end;

function GetDTA: DTAptr;
var
   regs            : record
                      ax,bx,cx,dx,bp,si,di,ds,es,flags: integer;
                     end;
begin;

 regs.ax:=$2F00;
 MsDos(regs);                    {Get the current DTA}
 GetDTA := Ptr(regs.es,regs.bx);
end;

procedure leave(rc:integer);
{Return to Dos with a return code }
var
   regs            : record
                      ax,bx,cx,dx,bp,si,di,ds,es,flags: integer;
                     end;
begin;

 regs.ax:=$4C00+rc;
 MsDos(regs);                    {Get the current DTA}
end;
procedure errmsg(rc:integer);
{write the error message associated with an error number}
var
   str_rc          : string[255];
begin;
Case rc of
 0: stwriteln('No error encountered');
 1: stwriteln('Invalid function number');
 2: stwriteln('File not found');
 3: stwriteln('Path not found');
 4: stwriteln('Too many files open');
 5: stwriteln('Access denied');
 6: stwriteln('Invalid handle');
 7: stwriteln('Memory control block destroyed');
 8: stwriteln('Insufficent memory');
 9: stwriteln('Invalid memory block address');
10: stwriteln('Invalid environment');
11: stwriteln('Invalid format');
12: stwriteln('Invalid access code');
13: stwriteln('Invalid data');
15: stwriteln('Invalid drive specified');
16: stwriteln('Attempt to remove the current directory');
17: stwriteln('Not same device');
18: stwriteln('No more files');
{ Non DOS error codes unique to this program }
20: stwriteln('No more paths in that direction');
255: stwriteln('Invalid parameters');
else str(rc,str_rc);
     stwriteln('Unknown error code: '+str_rc);
end;
end;

{
  Mainline begins here...
}

Begin;

 if (parm1_drive <> chr(0)    ) or      { Can't specify drive in 1st parm }
    (copy(cparm1,9,3) <> '   ') or      { nor an extension }
    (cparm2 <> '           '  ) then    { nor a second filename }
  begin;
   errmsg(255);
   leave(255);
  end;

 quietly := false;
 query   := false;

 parm:=''; { Convert PSP FCB1 to a string }
 for i:=1 to 8 do if cparm1[i]<>' ' then parm:=concat(parm,cparm1[i]);

 for i:= 1 to 2 do { see if do quietly or query were specified }
  begin;
   if copy(parm,1,1)='-' then
    begin;
     quietly := true;
     delete(parm,1,1);
    end
   else if copy(parm,1,1)='?' then
    begin;
     query := true;
     delete(parm,1,1);
    end;
  end;

 if parm = '' then  { User doesn't understand the syntax, so they them }
  begin;
   stwriteln('Climb syntax:');
   stwriteln(' ');
   stwriteln('CLIMB (-|?)UP      (d:)');
   stwriteln('CLIMB      DOWN');
   stwriteln('CLIMB      LEFT');
   stwriteln('CLIMB      RIGHT');
   leave(255);
 end;

 if Pos(parm,'UP') = 1 then parm:='UP'
    else if Pos(parm,'DOWN') = 1 then parm := 'DOWN'
    else if Pos(parm,'LEFT') = 1 then parm := 'LEFT'
    else if Pos(parm,'RIGHT') = 1 then parm := 'RIGHT' else
  begin;
   if not quietly then errmsg(255); { Bad syntax, so abort }
   leave(255);
  end;

 get_current_path(drive,current_path,rc); { Where are we now? }

 if rc<>0 then                            { If we get an error here     }
  begin;                                  { DOS is having REAL problems }
   if not quietly then errmsg(rc);
   leave(rc);
  end;

  if (current_path = '\') and             { Only valid direction from   }
    ((Pos(parm,'LEFT')=1) or              { root is down...             }
     (Pos(parm,'UP')=1) or
     (Pos(parm,'RIGHT')=1)) then
   begin;
    if not quietly then
     begin;
      errmsg(20);
      stwrite('Current path: ');
      if drive <> 0 then stwriteln(chr(64+drive)+':\')
                  else stwriteln('\');
     end;
    leave(20);
   end;

 if Pos(parm,'UP') = 1 then             { All going up is truncating the }
   new_path := trunc_path(current_path) { lowest branch...               }
  else
  begin;

   low_path:='';
   if Pos(parm,'LEFT') = 1 then
   for i:=1 to 8 do low_path:=low_path+chr(0) { low_path is now at low value }
   else for i:=1 to 8 do low_path:=low_path+chr(255); { or high value        }

   current_DTA:=GetDTA;                 { DOS leaves info in the DTA so we'd }
                                        { better find it...                  }

   this_branch:=this_path(current_path);{ Isolate the name of this branch    }

   if Pos(parm,'DOWN') = 1 then
    upper_path:=current_path            { look in this directory for DOWN    }
    else upper_path:=trunc_path(current_path); { else, we have to look above }

   if upper_path <> '\' then            { Specify the search string          }
    lookup_pattern:=upper_path+'\*.*'+chr(0)
    else lookup_pattern:='\*.*'+chr(0);

   fscall(find_1st,drive,lookup_pattern,directory,rc); { Go a'hunting        }

   while rc = 0 do
    begin;                               { Got one... (maybe)                }
     if ((current_DTA^.attribute and directory) = directory) and
         (current_DTA^.fname[1] <> '.') then  { Real directory?              }
      begin;
       if Pos(parm,'LEFT') = 1 then
        begin;
         if (asz2str(current_DTA^.fname) > low_path) and
            (asz2str(current_DTA^.fname) < this_branch)
          then low_path := asz2str(current_DTA^.fname);
        end
 {
  If we are going left, we want a directory that is lower than the current
  branch, yet higher than anything else...
 }
        else if Pos(parm,'RIGHT') = 1 then
         begin;
          if (asz2str(current_DTA^.fname) < low_path) and
             (asz2str(current_DTA^.fname) > this_branch)
           then low_path := asz2str(current_DTA^.fname);
         end
 {
  Right directories, on the other hand, are higher than this directory, but
  lower than everything else.
 }
         else
          begin;
            if (asz2str(current_DTA^.fname) < low_path)
             then low_path := asz2str(current_DTA^.fname);
          end;
 {
  And finally down directories are the lowest directory in the current
  directory.
 }
      end;
     fscall(find_nxt,drive,lookup_pattern,directory,rc); { Do it again... }
    end;

  if (copy(low_path,1,1)=chr(255))
     or (copy(low_path,1,1)=chr(0)) then  {If not set, we didn't find anything}
   begin;                                 {that qualified                     }
    if not quietly then
     begin;
      errmsg(20);
      stwrite('Current path: ');
      if drive=0 then stwriteln(current_path)
                  else stwriteln(chr(64+drive)+':'+current_path);
     end;
    leave(20);
   end;

  if upper_path <> '\' then               { Make the string for chdir }
   new_path := upper_path+'\'+low_path
  else new_path := '\'+low_path;

 end;

 if query then                            { If only a query, just show it }
  begin;
   if not quietly then
    begin;
     stwriteln('Current path: '+current_path);
     stwrite('Next path '+parm+': ');
     if drive = 0 then stwriteln(new_path)
        else stwriteln(chr(64+drive)+':'+new_path);
    end;
   leave(0)
  end
 else
  begin;

   fscall(chdir,drive,new_path,0,rc);     { else, chnage to it }

   if not quietly then
    begin;
     if  rc = 0  then
      begin;
       stwrite('Climbed '+parm+' to ');
       if drive = 0 then stwriteln(new_path)
        else stwriteln(chr(64+drive)+':'+new_path);
      end
      else errmsg(rc);
    end;

   Leave(rc);
  end;
end.
