File Coverage

blib/lib/App/Prove/Watch.pm
Criterion Covered Total %
statement 64 82 78.0
branch 9 22 40.9
condition 5 11 45.4
subroutine 14 19 73.6
pod 0 4 0.0
total 92 138 66.6


line stmt bran cond sub pod time code
1             package App::Prove::Watch;
2             $App::Prove::Watch::VERSION = '0.1';
3              
4 2     2   124370 use strict;
  2         3  
  2         71  
5 2     2   6 use warnings;
  2         3  
  2         51  
6              
7 2     2   1220 use App::Prove;
  2         78183  
  2         66  
8 2     2   1282 use Filesys::Notify::Simple;
  2         5073  
  2         64  
9 2     2   15 use File::Basename;
  2         2  
  2         195  
10 2     2   11 use Getopt::Long qw(GetOptionsFromArray);
  2         3  
  2         12  
11              
12              
13             =head1 NAME
14              
15             App::Prove::Watch - Run tests whenever changes occur.
16              
17             =head1 VERSION
18              
19             version 0.1
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 2     2 0 2428 my $class = shift;
66 2         7 my ($args, $prove_args) = $class->_split_args(@_);
67            
68              
69 2         15 my $watcher = Filesys::Notify::Simple->new($args->{watch});
70 2         398 my $prove = $class->_get_prove_sub($args, $prove_args);
71              
72 2         13 return bless {
73             watcher => $watcher,
74             prove => $prove,
75             }, $class;
76             }
77              
78 2     2 0 9 sub prove { return $_[0]->{prove}->() };
79 0     0 0 0 sub watcher { return $_[0]->{watcher} };
80              
81             sub run {
82 1     1 0 5 my ($self, $count) = @_;
83            
84 1         4 $self->prove;
85              
86 1   50     35 $count ||= -1;
87 1         3 while ($count != 0) {
88             $self->watcher->wait(sub {
89 1     1   40 my $doit;
90 1         3 foreach my $event (@_) {
91 1         53 my $file = basename($event->{path});
92 1 50       4 next if $file =~ m/^(?:\.~)/;
93            
94 1         3 $doit++;
95            
96             }
97            
98 1 50       3 if ($doit) {
99 1         3 $self->prove();
100 1         23 $count--;
101             }
102 1         4 });
103             }
104             }
105              
106              
107             sub _split_args {
108 2     2   4 my ($class, @args) = @_;
109            
110 2         1 my (@ours, @theirs);
111            
112 2         8 while (@args) {
113 1         2 local $_ = shift @args;
114 1 50 33     11 if ($_ eq '--watch' || $_ eq '--run') {
115 1         6 push(@ours, $_, shift @args);
116             }
117             else {
118 0         0 push(@theirs, $_);
119             }
120             }
121            
122 2         3 my %ours;
123 2         9 GetOptionsFromArray(\@ours, \%ours,
124             'watch=s@',
125             'run=s',
126             );
127            
128 2 50 33     527 if (!$ours{watch} || !@{$ours{watch}}) {
  0         0  
129 2         5 $ours{watch} = ['.']
130             }
131            
132 2         6 return (\%ours, \@theirs);
133             }
134              
135             sub _get_prove_sub {
136 2     2   4 my ($class, $args, $prove_args) = @_;
137            
138 2         5 my $handle_alert = $class->_get_notification_sub;
139            
140 2         3 my $last;
141             my $prove;
142            
143 2 100       5 if ($args->{run}) {
144 1 50       4 if (ref $args->{run}) {
145 1         2 $prove = $args->{run};
146             }
147             else {
148             $prove = sub {
149 0     0   0 my $ret = system($args->{run});
150            
151 0 0       0 return $ret == 0 ? 1 : 0;
152 0         0 };
153             }
154             }
155             else {
156             $prove = sub {
157 0     0   0 my $app = App::Prove->new;
158            
159 0         0 $app->process_args(@$prove_args);
160            
161 0 0       0 return $app->run ? 1 : 0;
162 1         4 };
163             }
164            
165             return sub {
166 2     2   3 my $ret = $prove->();
167            
168 2 50 66     502 if (defined $last && $ret != $last) {
169 0         0 my $msg;
170 0 0       0 if ($ret) {
171 0         0 $msg = "Tests are now passing.";
172             }
173             else {
174 0         0 $msg = "Tests are now failing.";
175             }
176            
177 0         0 $handle_alert->($msg);
178             }
179 2         3 $last = $ret;
180            
181 2         3 return $ret;
182 2         7 };
183             }
184              
185              
186             sub _get_notification_sub {
187 2     2   3 my $has_desk_note = eval {
188 2         350 require Log::Dispatch::DesktopNotification;
189             };
190            
191 2 50       10 if ($has_desk_note) {
192 0         0 my $notify = Log::Dispatch::DesktopNotification->new(
193             name => 'notify',
194             min_level => 'notice',
195             app_name => 'provewatcher',
196             );
197            
198             return sub {
199 0     0   0 $notify->log(
200             level => 'notice',
201             message => shift,
202             );
203             }
204 0         0 }
205             else {
206 2     0   10 return sub {};
  0            
207             }
208             }
209              
210             =head1 TODO
211              
212             =over 2
213              
214             =item *
215              
216             Ironically, for a TDD tool, there's not enough tests.
217              
218             =item *
219              
220             The C script needs documentation.
221              
222             =back
223              
224             =head1 AUTHORS
225              
226             Chris Reinhardt
227             crein@cpan.org
228            
229             =head1 COPYRIGHT
230              
231             This program is free software; you can redistribute
232             it and/or modify it under the same terms as Perl itself.
233              
234             The full text of the license can be found in the
235             LICENSE file included with this module.
236              
237             =head1 SEE ALSO
238              
239             L, L, perl(1)
240              
241             =cut
242            
243              
244             1;