Update docs
[catagits/Catalyst-Devel.git] / lib / Catalyst / Watcher.pm
CommitLineData
8462f41e 1package Catalyst::Watcher;
2
3use Moose;
4use Moose::Util::TypeConstraints;
5
6use File::Find;
7use File::Modified;
8use File::Spec;
9use Time::HiRes qw/sleep/;
10use namespace::clean -except => 'meta';
11
12has interval => (
13 is => 'ro',
14 isa => 'Int',
15 default => 1,
16);
17
18has regex => (
19 is => 'ro',
20 isa => 'RegexpRef',
21 default => sub { qr/(?:\/|^)(?!\.\#).+(?:\.yml$|\.yaml$|\.conf|\.pm)$/ },
22);
23
24my $dir = subtype
25 as 'Str'
26 => where { -d $_ }
27 => message { "$_ is not a valid directory" };
28
29my $array_of_dirs = subtype
30 as 'ArrayRef[Str]',
31 => where { map { -d } @{$_} }
32 => message { "@{$_} is not a list of valid directories" };
33
34coerce $array_of_dirs
35 => from $dir
36 => via { [ $_ ] };
37
38has directory => (
39 is => 'ro',
40 isa => $array_of_dirs,
41 default => sub { [ File::Spec->rel2abs( File::Spec->catdir( $FindBin::Bin, '..' ) ) ] },
42 coerce => 1,
43);
44
45has follow_symlinks => (
46 is => 'ro',
47 isa => 'Bool',
48 default => 0,
49);
50
51has _watched_files => (
52 is => 'ro',
53 isa => 'HashRef[Str]',
54 lazy_build => 1,
55 clearer => '_clear_watched_files',
56);
57
58has _modified => (
59 is => 'rw',
60 isa => 'File::Modified',
61 lazy_build => 1,
62);
63
64sub _build__watched_files {
65 my $self = shift;
66
67 my $regex = $self->regex;
68
69 my %list;
70 finddepth(
71 {
72 wanted => sub {
73 my $file = File::Spec->rel2abs($File::Find::name);
74 return unless $file =~ /$regex/;
75 return unless -f $file;
76
77 $list{$file} = 1;
78
79 # also watch the directory for changes
80 my $cur_dir = File::Spec->rel2abs($File::Find::dir);
81 $cur_dir =~ s{/script/..}{};
82 $list{$cur_dir} = 1;
83 },
84 follow_fast => $self->follow_symlinks ? 1 : 0,
85 no_chdir => 1
86 },
87 @{ $self->directory }
88 );
89
90 return \%list;
91}
92
93sub _build__modified {
94 my $self = shift;
95
96 return File::Modified->new(
97 method => 'mtime',
98 files => [ keys %{ $self->_watched_files } ],
99 );
100}
101
102sub find_changed_files {
103 my $self = shift;
104
105 my @changes;
106 my @changed_files;
107
108 sleep $self->interval if $self->interval > 0;
109
110 eval { @changes = $self->_modified->changed };
111 if ($@) {
112 # File::Modified will die if a file is deleted.
113 my ($deleted_file) = $@ =~ /stat '(.+)'/;
114 push @changed_files,
115 {
116 file => $deleted_file || 'unknown file',
117 status => 'deleted',
118 };
119 }
120
121 if (@changes) {
122 $self->_modified->update;
123
124 @changed_files = map { { file => $_, status => 'modified' } }
125 grep { -f $_ } @changes;
126
127 # We also need to check to see if a new directory was created
128 unless (@changed_files) {
129 my $old_watch = $self->_watched_files;
130
131 $self->_clear_watched_files;
132
133 my $new_watch = $self->_watched_files;
134
135 @changed_files
136 = map { { file => $_, status => 'added' } }
137 grep { !defined $old_watch->{$_} }
138 keys %{$new_watch};
139
140 return unless @changed_files;
141 }
142 }
143
144 return @changed_files;
145}
146
147__PACKAGE__->meta->make_immutable;
148
1491;
150
151__END__
152
153=head1 NAME
154
155Catalyst::Watcher - Watch for changed application files
156
157=head1 SYNOPSIS
158
714e3652 159 my $watcher = Catalyst::Watcher->new(
8462f41e 160 directory => '/path/to/MyApp',
161 regex => '\.yml$|\.yaml$|\.conf|\.pm$',
714e3652 162 interval => 3,
8462f41e 163 );
714e3652 164
8462f41e 165 while (1) {
166 my @changed_files = $watcher->watch();
167 }
168
169=head1 DESCRIPTION
170
171This class monitors a directory of files for changes made to any file
714e3652 172matching a regular expression. It correctly handles new files added to the
8462f41e 173application as well as files that are deleted.
174
175=head1 METHODS
176
177=head2 new ( directory => $path [, regex => $regex, delay => $delay ] )
178
179Creates a new Watcher object.
180
714e3652 181=head2 find_changed_files
8462f41e 182
714e3652 183Returns a list of files that have been added, deleted, or changed
184since the last time watch was called. Each element returned is a hash
185reference with two keys. The C<file> key contains the filename, and
186the C<status> key contains one of "modified", "added", or "deleted".
8462f41e 187
188=head1 SEE ALSO
189
714e3652 190L<Catalyst>, L<Catalyst::Restarter>, <File::Modified>
8462f41e 191
192=head1 AUTHORS
193
194Catalyst Contributors, see Catalyst.pm
195
8462f41e 196=head1 COPYRIGHT
197
714e3652 198This program is free software, you can redistribute it and/or modify
199it under the same terms as Perl itself.
8462f41e 200
201=cut