#!perl use strict; use warnings; use Getopt::Long; use Filesys::Notify::Simple; use Pod::Usage; use File::Spec; use App::watcher; use List::MoreUtils qw/ any /; use 5.008008; our $VERSION=$App::watcher::VERSION; my @dir; my @exclude_dir; # process does not die when received SIGTERM, on win32. my $signal=$^O eq 'MSWin32' ? 'KILL' : 'TERM'; GetOptions( 'dir=s@' => \@dir, 'exclude=s@' => \@exclude_dir, 'signal=s' => \$signal, 'send_only' => \my $send_only, 'h|help' => \my $help, 'v|version' => \my $version, 'filter=s@' => \my @filters, ) or pod2usage; $version and do { print "watcher: $VERSION\n"; exit 0 }; pod2usage(1) if $help; pod2usage(1) unless @ARGV; @dir = ('.') unless @dir; $_ = qr/$_/ for @filters; # default filter push @filters, qr!^\.[^\.]|[/\\][\._][^\.]|\.bak$|~$|_flymake\.(?:p[lm]|t)! unless @filters; if (@exclude_dir) { @exclude_dir = map { File::Spec->abs2rel($_) } @exclude_dir; } sub info { my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time); my $time = sprintf( "%04d-%02d-%02dT%02d:%02d:%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec ); print "[$time] ", join(' ', @_), "\n"; } my $pid; sub fork_and_start { undef $pid; $pid = fork; die "Can't fork: $!" unless defined $pid; if ( $pid == 0 ) { # child $SIG{INT} = $SIG{HUP} = $SIG{TERM} = 'DEFAULT'; exec @ARGV; die "Cannot exec: @ARGV"; } else { info("Forked process: @ARGV"); } } sub kill_pid { $pid or return; info("Killing the existing process by $signal (pid:$pid)"); kill $signal => $pid; waitpid( $pid, 0 ); } sub send_signal { info("Sending $signal to the existing process (pid:$pid)"); kill $signal => $pid; } info("watching: @dir"); fork_and_start(); exit(0) unless $pid; for my $sig (qw(TERM HUP INT)) { $SIG{$sig} = sub { info("SIG$sig received"); finalize(); }; } my $watcher = Filesys::Notify::Simple->new(\@dir); while (1) { my @restart; $watcher->wait(sub { my @events = @_; @events = grep { valid_file($_) } map { $_->{path} } @events; @restart = @events; }); next unless @restart; info("-- $_") for @restart; if ($send_only) { send_signal(); } else { kill_pid(); info("Successfully killed! Restarting the new process."); fork_and_start(); unless ($pid) { exit(0); } } } sub finalize { my $self = shift; if ($pid) { info("Terminate process: $pid"); kill 'TERM' => $pid; waitpid( $pid, 0 ); } exit 0; } sub valid_file { my ($file) = @_; my $rel = File::Spec->abs2rel($file); # default filter return if any { $rel =~ $_ } @filters; # exclude path filter return if any { index($rel, $_) == 0 } @exclude_dir; return 1; } __END__ =encoding utf8 =head1 NAME watcher - watch the file updates =head1 SYNOPSIS % watcher --dir . -- osascript -e 'tell application "Google Chrome" to reload active tab of window 1' --dir=. Directory to watch. --exclude Directory to ignore. --filter Regex of files to ignore --signal=HUP Sending signal to restart(Default: TERM)(EXPERIMENTAL) --send_only Sending signal without fork/exec(EXPERIMENTAL) -h --help show this help =head1 DESCRIPTION This command watches the directory updates, and run the commands. If no filter is provided via the C<--filter> option, a default filter will be used. This default filter ignores files and directories prefixed with a dot, F<.bak> files, and files ending with a F<~>. =head1 Sending SIGHUP without restart process (EXPERIMENTAL) watcher can send SIGHUP without process restarting. % watcher --signal=HUP --send_only -- ... =head1 AUTHOR Tokuhiro Matsuno E<lt>tokuhirom AAJKLFJEF@ GMAIL COME<gt> =head1 SEE ALSO L<Filesys::Notify::Simple> =head1 LICENSE Copyright (C) Tokuhiro Matsuno This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.