#!/usr/bin/env perl
# watch.pl  Perl implementation of Linux 'watch' utility, suitable for Solaris,
#           AIX and other operating systems.
#
# Copyright (c) 2012 Graham Jenkins <grahjenk@cpan.org>. All rights reserved.
# This program is free software; you can redistribute it and/or modify it under
# the same terms as Perl itself. Revised: 2020-03-31

use strict;
use warnings;
use File::Basename;
use Getopt::Std;
use Curses;
use vars qw($VERSION);
$VERSION=1.08;
$Getopt::Std::STANDARD_HELP_VERSION=1;

# Format-time subroutine
sub format_time { # Usage: format_time(integer)
  my ($s,$m,$h,$D,$M,$Y) = localtime($_[0]);
  return ($Y+1900).substr("00".($M+1),-2).substr("00".$D,-2)."-".
    substr("00".$h,-2).":".substr("00".$m,-2).":".substr("00".$s,-2)
}

# Set default delay, check usage, assemble command
my %opts=('n'=>2);
my $BadOpt;
getopts('dn:',\%opts) or $BadOpt="Y";
die "Usage: ".basename($0)." [-d] [-n secs] Command\n".
    " e.g.: ".basename($0)." -n 1 \"ls -lt | head -19\"\n\n".
    "Options: -n     interval between updates (default=2)\n".
    "         -d     highlight changes between updates\n"
  if ( defined($BadOpt) or ($#ARGV<0) or ($opts{n}!~m/^\d+$/) );
my $command;
for my $a (@ARGV) {
  if( defined($command) ) {$command.=" ".$a}
  else                    {$command=$a     }
}

# Force screen-size initialisation, define heading, set exit trap
my ($oldcols,$oldlines)=(-1,-1);
my $heading="Every ".$opts{n}."s: ".$command;
my (@oldline);
initscr();
$SIG{INT} = sub { done() };
sub done { endwin(); exit(0) };

# Loop until trap is sprung
while (1) {
  # Get current screen size; if it changed, clear each line
  resize(LINES,COLS);
  if ( (COLS!=$oldcols) or (LINES!=$oldlines) ) {
    clear();
    for (my $j=1;$j<LINES;$j++) { $oldline[$j]="" }
    ($oldcols,$oldlines)=(COLS,LINES)
  }
  # Display heading and current time
  move(0,0); clrtoeol(); addstr(substr($heading,0,int(COLS)-17));
  my $t=format_time(time());
  addstr(0,int(COLS)-length($t),$t);
  # Execute the command and place resultant lines in array
  my @result=`$command`;
  for (my $j=1;$j<LINES;$j++) {
    my $line="";
    if (defined($result[$j-1])) {
      $line=substr($result[$j-1],0,int(COLS));
      chomp($line)
    }
    # Optionally highlight the lines which changed
    move($j,0); clrtoeol();
    for (my $k=0;$k<length($line);$k++) {
      if ( length($oldline[$j]) < $k ) { $oldline[$j].=" " }
      if ( ( defined($opts{'d'}) ) and
           ( substr($line,$k,1) ne substr($oldline[$j],$k,1) ) ) { 
        attron(A_STANDOUT); addstr(substr($line,$k,1)); attroff(A_STANDOUT)
      } else {
        addstr(substr($line,$k,1))
      }
    }
    $oldline[$j]=$line
  }
  refresh();
  sleep($opts{n})
}

=head1 NAME

watch - Runs a designated command repeatedly 

=head1 DESCRIPTION

C<watch> is a perl implementation of the equivalent command found on many
Linux systems. It can be executed on Solaris, AIX and other systems.

=head1 README

A perl implementation of the watch command found on many Linux systems.

=head1 PREREQUISITES

This script requires C<Curses>.

=head1 OSNAMES

linux, unix

=head1 SCRIPT CATEGORIES

UNIX/System_administration

=cut 
