program sleep;

(*
 *  sleep -- suspend execution for a specified time
 *  Copyright (C) 2000 Trane Francks
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *  
 *  Author and Maintainer:  Trane Francks <trane@gol.com>
 *
 *  To reach me by snail-mail, contact me at:
 *
 *                          Sento Hills Tsurukawa #305
 *                          1856-4 Kanai-cho, Machida City
 *                          Tokyo 195-0071
 *                          JAPAN
 *
 *  Comments, suggestions, and bug reports should be sent to me, preferably
 *  by e-mail. I'd like to hear from you if you find this program useful.
 *  Constructive criticism is nice; flames will go quietly into /dev/null.
 *)

(*
 *  NAME
 *      sleep -- suspend execution for a specified time
 *
 *  SYNOPSIS
 *      sleep [--help] [--version] seconds
 *
 *  DESCRIPTION
 *      The sleep command continues running until the specified number of
 *      seconds has elapsed.  sleep can delay execution of a program or
 *      produce periodic execution in conjunction with shell commands.
 *
 *      The seconds argument can be either a number of seconds or a more
 *      general time description of the form nhnmns, with nh, nm, and s being
 *      optional. The maximum sleep duration is 2,147,483,647 seconds, which
 *      works out to 596,523 hours, 14 minutes and 7 seconds. That, I think,
 *      should be just about long enough for anybody. ;-)
 *
 *  OPTIONS
 *      sleep accepts the following options:
 *
 *      --help
 *      /? *        displays help and exits successfully. When this option is
 *                  used, sleep does not suspend execution. This option cannot
 *                  be used with other options.  * Note: sleep checks for the
 *                  switchar defined in DOS. If you have set a different
 *                  switchar in DOS, sleep will honour your preferred setting.
 *
 *      --version
 *      /v *        displays version and exits successfully. When this option
 *                  is used, sleep does not suspend execution. This option
 *                  cannot be used with other options.  * Note: sleep checks
 *                  for the switchar defined in DOS. If you have set a
 *                  different switchar in DOS, sleep will honour your preferred
 *                  setting.
 *
 *  EXAMPLES
 *      sleep 360           sleeps for 360 seconds
 *      sleep 5m            sleeps for 5 minutes
 *      sleep 1h15m30s      sleeps for 1 hour, 5 minutes, 30 seconds
 *
 *  DIAGNOSTICS
 *      Possible exit status values are:
 *
 *      0    Successful completion.
 *
 *      2    Failure because seconds value was not specified or is invalid.
 *)

(* --------------------------------------------------------------------- *)
(* Simple routines to slice the CPU in Win, OS/2, Dos, DV, and DoubleDos *)
(* Written by Pete Rocca (support@mbcc.com), Freeware source code        *)
(*                                                                       *)
(* You may use this code in your programs, however please make a small   *)
(* mention in your documentation, or let me know that you're using it.   *)
(*                                                                       *)
(* Fido: 1:2401/305,  BBS: 519-660-8981,  Email: support@mbcc.com        *)
(* --------------------------------------------------------------------- *)


uses Crt, Dos;

const
   version   = '1.0';  { program version number }
   DOS4	     = 0;      { Dos 4 and under        - no slicing }
   DOS5	     = 1;      { Dos 5 and up                        }
   DV	     = 2;      { DV 2.x and up                       }
   WINDOWS   = 3;      { Windows 2.x and up                  }
   DOUBLEDOS = 4;      { DoubleDos 1.x and up                }
   OS1	     = 5;      { OS/2 1.x and under     - no slicing }
   OS2	     = 6;      { OS/2 2.x and up                     }

var
   code		     : integer;   { errorcode returned from val procedure }
   seconds	     : longint;   { number of seconds for program to sleep }
   userInput	     : string;    { time to sleep as entered on command line }
   SystemEnvironment : byte;      { stores ID of operating system }
   switch	     : string;    { holds uppercased userInput }
   counter	     : byte;      { counter used with for loop }
   switchar	     : char;      { the DOS command-switch character }


function getswitchar: char;
(*
 *  gets and returns the DOS switch character
 *)
var
   regs	: registers; { holds packed registers for switchar }
begin
   regs.ax := $37 shl 8;
   with regs do
      msdos(regs);
   getswitchar := chr(regs.dx and $00ff)
end;

(* BEGIN MODIFIED CODE BY PETE ROCCA *)

procedure DetectEnvironment;
var
   R : Registers;
begin
   SystemEnvironment := DOS4;
   R.AX := $3000;
   Intr($21,R);
   if R.AL = 10 then
      SystemEnvironment := OS1
   else
      if R.AL > 19 then
	 SystemEnvironment := OS2
      else
	 if R.AL > 4  then
	    SystemEnvironment := DOS5;
   R.AX := $E400;
   Intr($21,R);
   if (R.AL = $01) or (R.AL = $02) then
      SystemEnvironment := DOUBLEDOS;
   R.AX := $4680;
   Intr($2F,R);
   if R.AX = $0000 then
      SystemEnvironment := WINDOWS;
   R.AX := $1600;
   Intr($2F,R);
   if (R.AL <> $00) and (R.AL <> $80) then
      SystemEnvironment := WINDOWS;
   R.AX := $2B01;
   R.CX := $4445;
   R.DX := $5351;
   Intr($21,R);
   if R.AL <> $FF then
      SystemEnvironment := DV;
end;

procedure SliceTimer;
begin
   case SystemEnvironment of
      DV :        asm
                     MOV AX,$1000
                     INT $15
                  end;
      DOS5,
      WINDOWS,
      OS2 :       asm
                     MOV AX,$1680
                     INT $2F
                  end;
      DOUBLEDOS : asm
                     MOV AX,$EE01
                     INT $21
                  end;
      OS1,
      DOS4:       { no slicing };
   end;
end;

(* END MODIFIED CODE BY PETE ROCCA *)


function convert2seconds(userInput : string): longint;
(*
 *  Parses userInput and returns number of seconds from string. If string
 *  contains invalid formatting or characters or the parsed value is out of
 *  valid longint range, returns -1.
 *)
const
   h = 0;
   m = 1;
   s = 2;
var
   timeValue	: longint; { current time value }
   seconds	: longint; { total accumulated seconds }
   currentDigit	: byte;    { current digit in parsed string }
   numberString	: string;  { holds numeric string for later conversion }
   code		: integer; { errorcode returned by val }
   counter	: byte;    { string-position counter }
   tempValue	: longint; { temp holder for range checking }
   timeUnit	: byte;    { hours, minutes or seconds defined in const }
begin { convert2seconds }
   timeValue := 0;
   seconds := 0;
   currentDigit := 0;
   numberString := '';
   for counter := 1 to length(userInput) do
   begin { parse userInput }
      val(userInput[counter], currentDigit, code);
      if (code = 0) then { it's a number }
	 numberString := concat(numberString, userInput[counter])
      else { it's a letter }
      begin { determine timeUnit and calculate time }
	 if (userInput[counter] in ['h', 'H']) then
	    timeUnit := h
	 else if (userInput[counter] in ['m', 'M']) then
	    timeUnit := m
	 else if (userInput[counter] in ['s', 'S']) then
	    timeUnit := s
	 else
	 begin { invalid userInput }
	    convert2seconds := -1;
	    exit;
	 end; { invalid userInput }
	 val(numberString, timeValue, code);
	 if (code <> 0) then { oops, we somehow got a bad value }
	 begin { bail }
	    convert2seconds := -1;
	    exit;
	 end; { bail }
	 begin { calculation and variable reset }
	    case timeUnit of
	      h : begin { convert hours to seconds }
		     tempValue := (timeValue * 60 * 60);
		  end; { convert hours to seconds }
	      m : begin { convert minutes to seconds }
		     tempValue := (timeValue * 60);
		  end; { convert minutes to seconds }
	      s : begin { add seconds to total seconds }
		     tempValue := timeValue;
		  end; { add seconds to total seconds }
	    end; { case timeUnit of }
	    if (tempValue <= (maxLongInt - seconds)) then
	       seconds := seconds + tempValue
	    else
	    begin { out of range }
	       convert2seconds := -1;
	       exit;
	    end; { out of range }
	    numberString := '';
	    timeValue := 0;
	    currentDigit := 0;
	 end; { calculation and variable reset }
      end; { determine timeUnit and calculate time }
   end; { parse userInput }
   if (numberString <> '') then { userInput has trailing digits }
   begin { convert trailing digits to seconds }
      val(numberString, timeValue, code);
      if (code <> 0) then { we got a bad value }
      begin { bail out }
	 convert2seconds := -1;
	 exit;
      end { bail out }
      else
      begin { check range }
	 if (timeValue <= (maxLongInt - seconds)) then
	    seconds := seconds + timeValue
	 else
	 begin { out of range }
	    convert2seconds := -1;
	    exit;
	 end; { out of range }
      end; { check range }
   end; { convert trailing digits to seconds }
   convert2seconds := seconds;
end; { convert2seconds }

procedure showhelp;
begin { procedure showhelp }
   writeln('sleep ', version);
   writeln('Usage: sleep [#h#m]#[s]');
   writeln;
   writeln('Example: sleep 1h15m');
   writeln('         sleep 30');
end; { procedure showhelp }


procedure displayversion;
(*
 *  display version information
 *)
begin { procedure display version }
   writeln('sleep ', version);
   writeln('Copyright (C) 2000 Trane Francks');
   writeln('sleep comes with NO WARRANTY');
   writeln('to the extent permitted by law.');
   writeln('You may redistribute copies of sleep');
   writeln('under the terms of the GNU General Public License.');
   writeln('For more information about these matters,');
   writeln('see the file LICENSE.TXT.');
   writeln;
   writeln('Report bugs to "Trane Francks" <trane@gol.com>');
   writeln('Snail-mail address: Trane Francks');
   writeln('                    Sento Hills Tsurukawa #305');
   writeln('                    1856-4 Kanai-cho, Machida City');
   writeln('                    Tokyo 195-0071');
   writeln('                    JAPAN');
end; { procedure display version }

procedure snooze(seconds : longint);
(*
 *  This is where the sleeping takes place. Gets the time and watches to see
 *  when the seconds change. Counter ticks for each change until number of
 *  seconds has elapsed. Gives up time slices to OS to be friendly.
 *)
var
   counter : longint; { track number of seconds already slept }
   hrs	   : word;    { hours segment of current time }
   min	   : word;    { minutes segment of current time }
   sec	   : word;    { seconds segment of current time }
   hun	   : word;    { hundreds segment of current time }
   oldsec  : word;    { store old sec segment for comparison }
   slice   : byte;    { counter for number of slices to give up }
   key	   : char;    { value of key pressed }
begin { snooze }
   gettime(hrs, min, sec, hun);
   counter := 0;
   repeat
      oldsec := sec;
      repeat
	 gettime(hrs, min, sec, hun);
	 for slice := 1 to 32 do
	    SliceTimer;
	 if keypressed then
	 begin { process keystroke }
	    key := readkey;
	    if (key = #3) then { user pressed Ctrl-C }
	       halt(130);
	 end; { process keystroke }
      until (oldsec <> sec);
      if (oldsec = 59) then
	 counter := counter + (sec + 1)
      else
	 counter := counter + (sec - oldsec);
   until (counter >= seconds);
end; { snooze }



begin { program sleep }
   directVideo := false; { use BIOS screen writes }
   seconds := 0;
   detectenvironment; { determine OS so we can time-slice }
   switchar := getswitchar; { get command-line switch char from DOS }
   if (paramcount = 0) or (paramcount > 1) then
   begin { incorrect number of parameters }
      writeln('sleep: incorrect number of parameters');
      showhelp;
      halt(2);
   end; { incorrect number of parameters }
   userInput := paramstr(1);
   val(userInput, seconds, code);
   if (code = 0) then { user entered seconds only }
   begin { check validity }
      if (seconds = 0) then
	 halt(0)
      else if (seconds > 0) then
	 snooze(seconds)
      else
      begin { invalid value }
	 writeln('sleep: ', paramstr(1), ': invalid value');
	 showhelp;
	 halt(2);
      end; { invalid value }
   end { check validity }
   else
   begin { parse userInput }
      { first, check to see if we're working with option switches }
      switch := userInput;
      for counter := 1 to length(switch) do
	 switch[counter] := upcase(switch[counter]);
      if (switch = '--HELP') or (switch = switchar + '?') then
      begin { show help and exit gracefully }
	 showhelp;
	 halt(0);
      end { show help and exit gracefully }
      else if (switch = '--VERSION') or (switch = switchar + 'V') then
      begin { display version and exit gracefully }
	 displayversion;
	 halt(0);
      end; { display version and exit gracefully }
      seconds := convert2seconds(userInput);
      if (seconds = -1) then { the input string was invalid }
      begin { bail out finally }
	 writeln('sleep: ', userInput, ': invalid value');
	 showhelp;
	 halt(2);
      end { bail out finally }
      else if (seconds > 0) then
	 snooze(seconds);
   end; { parse userInput }
   halt(0);
end. { program sleep }