#!/usr/bin/perl

# atchange - monitor a file and run a command when it changes
# https://kinzler.com/me/home.html#other

# by Jeff Haemer
#	and a tip o' the hat to Tom Schneider
#       who wrote the original version as a shell script

# version = 2.03 of atchange 1997 Jan 9
# delay time is 0.25 seconds

# modified 1998 Aug 7 by steve@kinzler.com
# made Perl4 compatible, changed usage string

#  For current version and other information about this program, see:
#  http://www-lmmb.ncifcrf.gov/~toms/atchange.html

#  Tom Schneider
#  National Cancer Institute
#  Laboratory of Mathematical Biology
#  Frederick, Maryland  21702-1201
#  toms@ncifcrf.gov
#  http://www-lmmb.ncifcrf.gov/~toms/

$0 =~ s(.*/)();			# basename
$usage = "usage: $0 [ filename cmd | command_file ]";
@ARGV || die $usage;		# check for proper invocation

$shell = $ENV{"SHELL"} ? $ENV{"SHELL"} : "/bin/sh";
open(SHELL, "|$shell") || die "Can't pipe to $shell: $!";
select(SHELL); $| = 1;

if (@ARGV > 1) {		# it's a file and a command
	$file = shift;				# peel off the filename
	$cmd{$file} = join(" ", @ARGV) . "\n";	#	and the command
	$old{$file} = (stat($file))[9];	# mod time.
} else {			# it's a program
	open(PGM, shift) || die "Can't open $_: $!";
	$/ = "";			# paragraph mode
	while(<PGM>) {			# first read the program
		s/#.*\n/\n/g;
		($file, $cmd) = /(\S*)\s+([^\000]+)/;
		$cmd{$file} = $cmd;
		unless ($file) { print $cmd{$file}; next; }
		if ($file && ! $cmd{$file}) { warn "odd line"; next; };
		$old{$file} = (stat($file))[9];	# mod time.
	}
}

while(1) {
	# sleep 1;		# wait a second, then
	select(undef, undef, undef, 0.25); # wait a quarter second, then
	foreach (keys %cmd) {	#	rip through the whole list
		&atchange($_);
	}
}
close(SHELL);

sub atchange {		# if $file has changed, do $cmd{$file}
	local($file) = @_;
	local($new);

	$new = (stat($file))[9];
	return 0 if ($old{$file} == $new);
	while (1) {			# wait until it stops changing
		$old{$file} = $new;
		sleep 1;
		$new = (stat($file))[9];
		if ($old{$file} == $new) {
			print $cmd{$file};
			return 1;
		}
	}
}
