-#!/bin/sh
-
-WATCH_FULL_FILE="$1"
-shift 1;
-
-if ! [ -e "$WATCH_FULL_FILE" ]; then
- echo "No such file or directory $WATCH_FULL_FILE";
-fi;
-
-WATCH_DIR=`dirname "$WATCH_FULL_FILE"`
-WATCH_FILE=`basename "$WATCH_FULL_FILE"`
-
-if which inotifywait > /dev/null 2>&1; then
- inotifywait -m -e close_write,moved_to,create $WATCH_DIR |
- while read -r directory events filename; do
- if [ "x$filename" = "x$WATCH_FILE" ]; then
- ( exec "$@" );
- fi;
- done;
-else
- CURR_TIME=$(stat -c '%Y' "$WATCH_FULL_FILE");
- while sleep 1; do
- NEW_TIME=$(stat -c '%Y' "$WATCH_FULL_FILE");
- if [ "x$NEW_TIME" != "x$CURR_TIME" ]; then
- CURR_TIME=$NEW_TIME
- ( exec "$@" );
- fi;
- done;
-fi;
+#!/usr/bin/perl
+# run_when_changed runs a command when a file (or files) is changed
+# and is released under the terms of the GNU GPL version 3, or any
+# later version, at your option. See the file README and COPYING for
+# more information.
+# Copyright 2017 by Don Armstrong <don@donarmstrong.com>.
+
+
+use warnings;
+use strict;
+
+use Pod::Usage;
+use Linux::Inotify2;
+use File::Basename;
+
+=head1 NAME
+
+run_when_changed - runs a command when a file (or files) is changed
+
+=head1 SYNOPSIS
+
+ run_when_changed [file1] [command ...]
+ run_when_changed [file1] [filen] -- [command ...]
+
+ Options:
+ --debug, -d debugging level (Default 0)
+ --help, -h display this help
+ --man, -m display manual
+
+If only a single file is to be watched, the first argument is the
+file, and all remaining arguments are the command. If multiple files
+are to be watched, all arguments until the first -- argument are files
+to be watched. If you wish to use a command containing -- and watch a
+single file, -- must be the second argument.
+
+=head1 EXAMPLES
+
+ run_when_changed file.Rnw make file.pdf;
+
+ run_when_changed file.Rnw file_2.Rnw -- make file.pdf;
+
+=cut
+
+my @USAGE_ERRORS;
+if (@ARGV < 2) {
+ push @USAGE_ERRORS,"You must provide at least a file and a command to run";
+}
+
+pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
+
+my ($watch_files,$command) =
+ identify_files_and_command(@ARGV);
+
+my $watching = set_up_file_watching($watch_files);
+
+watch_and_run_command($watching,$command);
+
+
+sub identify_files_and_command {
+ my (@arguments) = @_;
+
+ my $file_end=0;
+ my $command_start=1;
+ for my $i (0..$#arguments) {
+ if ($arguments[$i] eq '--') {
+ $file_end=$i-1;
+ $command_start=$i+1;
+ last;
+ }
+ }
+ my @files = @arguments[0..$file_end];
+ my @command = @arguments[$command_start..$#arguments];
+ return (\@files,\@command);
+}
+
+sub set_up_file_watching {
+ my ($files) = @_;
+ my $watching = {files => $files};
+ my $inotify = new Linux::Inotify2
+ or die "Unable to create new inotify object: $!";
+ for my $file (@{$files}) {
+ my $watched_dir = dirname($file);
+ next if exists $watching->{dirs_watched}{$watched_dir};
+ $inotify->watch($watched_dir,
+ IN_CLOSE_WRITE|IN_MOVED_TO|IN_CREATE,
+ ) or die "Unable to watch file $file: $!";
+ $watching->{dirs_watched}{$watched_dir} = 1;
+ }
+ $watching->{inotify} = $inotify;
+ return $watching;
+}
+
+sub watch_and_run_command {
+ my ($watching,$command) = @_;
+
+ while () {
+ my @events = $watching->{inotify}->read;
+ for my $event (@events) {
+ my $run_command = 0;
+ for my $file (@{$watching->{files}}) {
+ if ($event->w->name eq dirname($file) and
+ $event->name eq basename($file)
+ ) {
+ $run_command = 1;
+ }
+ }
+ if ($run_command) {
+ system(@{$command});
+ }
+ }
+ }
+}
+__END__