File Coverage

blib/lib/App/Prove/Watch.pm
Criterion Covered Total %
statement 80 92 86.9
branch 20 30 66.6
condition 9 14 64.2
subroutine 16 19 84.2
pod 0 4 0.0
total 125 159 78.6


line stmt bran cond sub pod time code
1             package App::Prove::Watch;
2             $App::Prove::Watch::VERSION = '0.3';
3              
4 2     2   111261 use strict;
  2         4  
  2         61  
5 2     2   7 use warnings;
  2         3  
  2         42  
6              
7 2     2   1079 use App::Prove;
  2         69726  
  2         61  
8 2     2   1013 use Filesys::Notify::Simple;
  2         3203  
  2         49  
9 2     2   10 use File::Basename;
  2         4  
  2         140  
10 2     2   8 use Getopt::Long qw(GetOptionsFromArray);
  2         4  
  2         10  
11              
12              
13             =head1 NAME
14              
15             App::Prove::Watch - Run tests whenever changes occur.
16              
17             =head1 VERSION
18              
19             version 0.3
20              
21             =head1 SYNOPSIS
22              
23             $ provewatcher
24              
25             =head1 DESCRIPTION
26              
27             Watches for changes in the current directroy tree and runs prove when there are
28             changes.
29              
30             =head1 ARGUMENTS
31              
32             C takes all the arguments that C takes with two additions:
33              
34             =head2 --watch
35              
36             Specifies what directories should be watched:
37              
38             # just watch lib
39             $ provewatcher --watch lib
40            
41             # watch lib and t
42             $ provewatcher --watch lib --watch t
43            
44             This defaults to C<.> if not given.
45              
46             =head2 --run
47              
48             Allows you to run something other than prove when changes happen. For example if
49             you where using L
50              
51             $ provewatcher --run 'dzil test'
52            
53             =head1 NOTIFICATIONS
54              
55             If you install L, desktop notifications will
56             be sent whenever the overall state of the tests change (failing to passing or
57             passing to failing).
58              
59             L is not listed as a prereq for this module,
60             it will not be installed by default when you install this module.
61              
62             =cut
63              
64             sub new {
65 3     3 0 3066 my $class = shift;
66 3         9 my ($args, $prove_args) = $class->_split_args(@_);
67            
68              
69 3         16 my $watcher = Filesys::Notify::Simple->new($args->{watch});
70 3         608 my $prove = $class->_get_prove_sub($args, $prove_args);
71              
72 3         16 return bless {
73             watcher => $watcher,
74             prove => $prove,
75             args => $args,
76             }, $class;
77             }
78              
79 4     4 0 9 sub prove { return $_[0]->{prove}->() }
80             sub watcher {
81 5     5 0 24 my $self = shift;
82            
83 5 100       10 if (@_) {
84 2         6 $self->{watcher} = shift;
85             }
86            
87 5         49 return $self->{watcher};
88             }
89              
90              
91             sub run {
92 2     2 0 7 my ($self, $count) = @_;
93            
94 2         15 $self->prove;
95              
96 2   50     5 $count ||= -1;
97 2         5 while ($count != 0) {
98             $self->watcher->wait(sub {
99 3     3   19 my $doit;
100 3         7 FILE: foreach my $event (@_) {
101 3         93 my $file = basename($event->{path});
102 3 50       9 next FILE if $file =~ m/^(?:\.[~#])/;
103            
104 3 100       11 if ($self->{args}{ignore}) {
105 2 100       11 next FILE if $file =~ $self->{args}{ignore};
106             }
107            
108 2         5 $doit++;
109            
110             }
111            
112 3 100       11 if ($doit) {
113 2         3 $self->prove();
114 2         27 $count--;
115             }
116 3         6 });
117             }
118             }
119              
120              
121             sub _split_args {
122 3     3   6 my ($class, @args) = @_;
123            
124 3         4 my (@ours, @theirs);
125            
126 3         9 while (@args) {
127 3         4 local $_ = shift @args;
128 3 50 66     19 if ($_ eq '--watch' || $_ eq '--run' || $_ eq '--ignore') {
      66        
129 3         6 push(@ours, $_, shift @args);
130             }
131             else {
132 0         0 push(@theirs, $_);
133             }
134             }
135            
136 3         3 my %ours;
137 3         14 GetOptionsFromArray(\@ours, \%ours,
138             'watch=s@',
139             'run=s',
140             'ignore=s@',
141             );
142            
143 3 50 33     808 if (!$ours{watch} || !@{$ours{watch}}) {
  0         0  
144 3         7 $ours{watch} = ['.']
145             }
146            
147 3 100       6 if ($ours{ignore}) {
148 1         2 my $merged = join('|', map { qr/$_/ } @{$ours{ignore}});
  1         16  
  1         3  
149 1         9 $ours{ignore} = qr/$merged/;
150             }
151            
152 3         9 return (\%ours, \@theirs);
153             }
154              
155             sub _get_prove_sub {
156 3     3   7 my ($class, $args, $prove_args) = @_;
157            
158 3         8 my $handle_alert = $class->_get_notification_sub;
159            
160 3         3 my $last;
161             my $prove;
162            
163 3 100       7 if ($args->{run}) {
164 2 50       6 if (ref $args->{run}) {
165 2         4 $prove = $args->{run};
166             }
167             else {
168             $prove = sub {
169 0     0   0 my $ret = system($args->{run});
170            
171 0 0       0 return $ret == 0 ? 1 : 0;
172 0         0 };
173             }
174             }
175             else {
176             $prove = sub {
177 0     0   0 my $app = App::Prove->new;
178            
179 0         0 $app->process_args(@$prove_args);
180            
181 0 0       0 return $app->run ? 1 : 0;
182 1         4 };
183             }
184            
185             return sub {
186 4     4   7 my $ret = $prove->();
187            
188 4 100 100     730 if (defined $last && $ret != $last) {
189 1         1 my $msg;
190 1 50       3 if ($ret) {
191 1         1 $msg = "Tests are now passing.";
192             }
193             else {
194 0         0 $msg = "Tests are now failing.";
195             }
196            
197 1         4 $handle_alert->($msg);
198             }
199 4         27 $last = $ret;
200            
201 4         5 return $ret;
202 3         11 };
203             }
204              
205              
206             sub _get_notification_sub {
207 3     3   3 my $has_desk_note = eval {
208 3         520 require Log::Dispatch::DesktopNotification;
209             };
210            
211 3 50       12 if ($has_desk_note) {
212 0         0 my $notify = Log::Dispatch::DesktopNotification->new(
213             name => 'notify',
214             min_level => 'notice',
215             app_name => 'provewatcher',
216             );
217            
218             return sub {
219 0     0   0 $notify->log(
220             level => 'notice',
221             message => shift,
222             );
223             }
224 0         0 }
225             else {
226 3     1   15 return sub {};
  1         2  
227             }
228             }
229              
230             =head1 TODO
231              
232             =over 2
233              
234             =item *
235              
236             Ironically, for a TDD tool, there's not enough tests.
237              
238             =back
239              
240             =head1 AUTHORS
241              
242             Chris Reinhardt
243             crein@cpan.org
244            
245             =head1 COPYRIGHT
246              
247             This program is free software; you can redistribute
248             it and/or modify it under the same terms as Perl itself.
249              
250             The full text of the license can be found in the
251             LICENSE file included with this module.
252              
253             =head1 SEE ALSO
254              
255             L, L, perl(1)
256              
257             =cut
258            
259              
260             1;