File Coverage

blib/lib/Test/Less.pm
Criterion Covered Total %
statement 100 265 37.7
branch 19 78 24.3
condition 4 15 26.6
subroutine 17 49 34.6
pod 0 28 0.0
total 140 435 32.1


line stmt bran cond sub pod time code
1             package Test::Less;
2 7     7   690776 use Spiffy 0.24 -Base;
  7         220  
  7         87  
3 7     7   15536 use Spiffy ':XXX';
  7     7   18  
  7     7   258  
  7         37  
  7         17  
  7         217  
  7         35  
  7         13  
  7         31  
4             our $VERSION = '0.11';
5             our @EXPORT = qw(run);
6              
7             field silent => 0;
8             field quiet => 0;
9             field verbose => 0;
10             field 'comment';
11              
12             field index =>
13             -init => '$self->index_class->new(file => $self->index_file)';
14             field index_file =>
15             $ENV{TEST_LESS_INDEX} ||
16             't/Test-Less/index.txt';
17             field index_class => 'Test::Less::Index';
18              
19 0     0 0 0 sub run {
20 0 0       0 my @args = @_ ? @_ : @ARGV;
21             @args = map {
22 0         0 $_ eq '-'
23 0 0       0 ? do {
24 0         0 local $/;
25 0         0 split /\s+/,
26             }
27             : ($_);
28             } @args;
29 0         0 Test::Less->new->run_command_line(@args);
30             }
31              
32 0     0 0 0 sub run_command_line {
33 0         0 my ($command, @arguments) = $self->parse_command_line(@_);
34 0         0 my $method ="run_$command";
35 0         0 $self->$method(@arguments);
36             }
37              
38 0     0 0 0 sub run_tag { $self->tag(@_) }
  0         0  
39 0     0 0 0 sub run_untag { $self->untag(@_) }
  0         0  
40 0     0 0 0 sub run_prove { $self->prove(@_) }
  0         0  
41 0     0 0 0 sub run_show { $self->show(@_) }
  0         0  
42 0     0 0 0 sub run_list {
43 0         0 print "$_\n" for $self->list(@_);
44             }
45              
46             # Action handlers
47 2     2 0 3933 sub tag {
48 2         9 my ($tags, $files) = $self->parse_tags_and_files(@_);
49 2 50       7 warn "No files specified\n" unless @$files;
50 2         5 for my $file (@$files) {
51 3         20 $self->tag_file($file, @$tags);
52             }
53             }
54              
55 0     0 0 0 sub untag {
56 0         0 my ($tags, $files) = $self->parse_tags_and_files(@_);
57 0         0 for my $file (@$files) {
58 0         0 $self->untag_file($file, @$tags);
59             }
60             }
61              
62 0     0 0 0 sub show {
63 0         0 for my $file ($self->parse_files(@_)) {
64 0         0 my @tags = $self->index->tags_for_file($file);
65 0         0 print "$file:\n @tags\n";
66             }
67             }
68              
69 0     0 0 0 sub list {
70 0         0 my $spec = $self->parse_spec(@_);
71 0         0 $self->index->files_matching_spec($spec);
72             }
73              
74 0     0 0 0 sub prove {
75 0         0 my ($flags, @args) = $self->parse_flags(@_);
76 0         0 exec {$self->bin_path('prove')} 'prove', @$flags, $self->list(@args);
  0         0  
77             }
78              
79             # Command parsers
80 0     0 0 0 sub parse_flags {
81 0         0 my @args = @_;
82 0         0 my @flags;
83 0   0     0 while (@args and $args[0] =~ /^-/) {
84 0         0 push @flags, shift @args;
85             }
86 0         0 return ([@flags], @args);
87             }
88              
89 5     5 0 10871 sub parse_spec {
90 5         10 my @args = @_;
91 5         10 my $spec = [];
92 5         8 for my $part (@args) {
93 9 100       24 if ($part =~ /,/) {
94 4         19 push @$spec, [split ',', $part];
95             }
96             else {
97 5         12 push @$spec, $part;
98             }
99             }
100 5         18 return $spec;
101             }
102              
103 0     0 0 0 sub parse_command_line {
104 0         0 my @words = @_;
105 0         0 while (my ($word) = @words) {
106 0 0       0 if ($word =~ /^(-q|--quiet)$/) {
107 0         0 $self->quiet(1);
108 0         0 shift @words;
109 0         0 next;
110             }
111 0 0       0 if ($word =~ /^(-v|--verbose)$/) {
112 0         0 $self->verbose(1);
113 0         0 shift @words;
114 0         0 next;
115             }
116 0 0       0 if ($word =~ /^(?:-f|--file)(?:=(\S+))?$/) {
117 0         0 shift @words;
118 0 0 0     0 my $file = $1 || shift(@words)
119             or $self->usage;
120 0         0 $self->index_file($file);
121 0         0 next;
122             }
123 0         0 last;
124             }
125              
126 0 0       0 my $word = shift(@words)
127             or $self->usage;
128 0 0       0 my $command =
    0          
    0          
    0          
    0          
129             $word =~ /^-?-t(ag)?$/ ? 'tag' :
130             $word =~ /^-?-u(ntag)?$/ ? 'untag' :
131             $word =~ /^-?-s(how)?$/ ? 'show' :
132             $word =~ /^-?-l(ist)?$/ ? 'list' :
133             $word =~ /^-?-p(rove)?$/ ? 'prove' :
134             $self->usage;
135 0         0 return ($command, @words);
136             }
137              
138 2     2 0 4 sub parse_tags_and_files {
139 2         6 my @args = @_;
140 2         4 my (@tags, @files);
141 2         9 while (@args) {
142 6 100       28 last unless $args[0] =~ /^[\w-]+$/;
143 4         19 push @tags, shift @args;
144             }
145 2         5 @files = @args;
146 2         7 return (\@tags, \@files);
147             }
148              
149 0     0 0 0 sub parse_files {
150 0         0 my @args = @_;
151             return @args
152 0 0       0 ? (@args)
153             : ($self->index->all_files);
154             }
155              
156             # Other routines
157 3     3 0 5 sub tag_file {
158 3         4 my $file = shift;
159 3         7 my @tags = @_;
160 3         75 my $index = $self->index;
161 3         136 for my $tag (@tags) {
162 6 50       15 $index->add_tag_file($tag, $file, $self->get_comment)
163             or $self->msg2("Can't add tag '$tag' to file '$file'");
164             }
165 3         9 $index->write;
166             }
167              
168 0     0 0 0 sub untag_file {
169 0         0 my $file = shift;
170 0         0 my @tags = @_;
171 0         0 my $index = $self->index;
172 0         0 for my $tag (@tags) {
173 0 0       0 $index->remove_tag_file($tag, $file)
174             or $self->msg2("Can't remove tag '$tag' from file '$file'");
175             }
176 0         0 $index->write;
177             }
178              
179 6     6 0 10 sub get_comment {
180 6         137 my $comment = $self->comment;
181 6 50 0     35 $comment = $ENV{TEST_LESS_COMMENT} || ''
182             unless defined $comment;
183 6         57 my $date = scalar(gmtime);
184 6         31 $date =~ s/^(mon|tue|wed|thu|fri|sat|sun)\s+//i;
185 6         11 $date .= ' GMT';
186 6         10 $comment =~ s/\$d/$date/ge;
  0         0  
187 6         8 $comment =~ s/\$u/$ENV{USER}/ge;
  0         0  
188 6         21 return $comment;
189             }
190              
191 0     0 0 0 sub bin_path {
192 0         0 require Config;
193 0         0 require File::Spec;
194 0         0 my $bin = $Config::Config{sitebin};
195 0         0 File::Spec->catfile($bin, shift);
196             }
197              
198 0     0 0 0 sub usage {
199 0         0 print <<'END'; exit 0;
  0         0  
200             Usage: test-less [options] command [arguments] [-]
201              
202             Options:
203             -file path_to_index_file
204             -quiet
205             -verbose
206              
207             Commands:
208             -help
209             -tag tags test-files
210             -untag tags test-files
211             -show test-files
212             -list tag-specification
213             -prove [prove-flags] tag-specification
214              
215             Options and commands may be abbreviated to their first letter.
216              
217             An argument of '-' is replaced by the contents of STDIN split on whitespace.
218              
219             END
220             }
221              
222             # I/O Stuff
223              
224 0     0 0 0 sub msg {
225 0         0 my @args = @_;
226 0         0 chomp $args[-1];
227 0         0 warn join '', @_, "\n";
228             }
229              
230 0     0 0 0 sub msg_threshold {
231 0 0       0 return 4 if $self->silent;
232 0         0 2 + $self->quiet - $self->verbose;
233             }
234              
235 0     0 0 0 sub msg1 {
236 0 0       0 return if $self->msg_threshold > 1;
237 0         0 $self->msg(@_);
238             }
239              
240 0     0 0 0 sub msg2 {
241 0 0       0 return if $self->msg_threshold > 2;
242 0         0 $self->msg(@_);
243             }
244              
245 0     0 0 0 sub msg3 {
246 0 0       0 return if $self->msg_threshold > 3;
247 0         0 $self->msg(@_);
248             }
249              
250 0     0 0 0 sub prompt {
251 0         0 print shift;
252 0         0 my $answer = <>;
253 0         0 chomp $answer;
254 0         0 return $answer;
255             }
256              
257             package Test::Less::Index;
258 7     7   43427 use Spiffy -base;
  7         19  
  7         69  
259 7     7   3224 use Spiffy ':XXX';
  7         18  
  7         34  
260              
261             field file => -init => 'die';
262             field index => -init => '$self->read';
263              
264 6     6   25 sub add_tag_file {
265 6         10 my ($tag, $file, $comment) = @_;
266 6 50       99 return unless -f $file;
267 6   50     25 $comment ||= '';
268 6         141 $self->index->{$tag}{$file} = $comment;
269 6         44 return 1;
270             }
271              
272 0     0   0 sub remove_tag_file {
273 0         0 my ($tag, $file) = @_;
274 0         0 my $index = $self->index;
275 0 0       0 return defined(delete $index->{$tag}{$file}) ? 1 : 0;
276             }
277              
278 0     0   0 sub all_files {
279 0         0 my $index = $self->index;
280 0         0 my %set = map {
281 0         0 map { ($_, 1) } keys %{$index->{$_}};
  0         0  
  0         0  
282             } keys %$index;
283 0         0 return sort keys %set;
284             }
285              
286 0     0   0 sub files_matching_spec {
287 0         0 my $spec = shift;
288 0         0 my $files = {};
289 0         0 for my $sub (@$spec) {
290 0 0       0 if (ref $sub) {
    0          
291 0         0 $self->list_add($files, $self->files_matching_list($sub));
292             }
293             elsif ($sub =~ /^\^(.*)/) {
294 0         0 my $term = $1;
295 0         0 $self->list_add($files, $self->all_files);
296 0         0 $self->list_del($files, $self->files_matching($term));
297             }
298             else {
299 0         0 $self->list_add($files, $self->files_matching($sub));
300             }
301             }
302 0         0 return sort keys %$files;
303             }
304              
305 0     0   0 sub files_matching_list {
306 0         0 my $spec = shift;
307 0         0 my $files = {};
308 0         0 $self->list_add($files, $self->all_files);
309 0         0 for my $term (@$spec) {
310 0 0       0 if ($term =~ s/^\^//) {
311 0         0 $self->list_del($files, $self->files_matching($term));
312             }
313             else {
314 0         0 $self->list_neg($files, $self->files_matching($term));
315             }
316             }
317 0         0 return keys %$files;
318             }
319              
320 0     0   0 sub files_matching {
321 0         0 my @files = ();
322 0         0 for my $term (@_) {
323 0 0       0 if ($term =~ /[^\w\-]/) {
324 0         0 push @files, $term;
325             }
326             else {
327 0         0 push @files, keys %{$self->index->{$term}};
  0         0  
328             }
329             }
330 0         0 return @files;
331             }
332              
333 0     0   0 sub list_add {
334 0         0 my $list = shift;
335 0         0 for my $file (@_) {
336 0         0 $list->{$file} = '';
337             }
338             }
339              
340 0     0   0 sub list_del {
341 0         0 my $list = shift;
342 0         0 for my $file (@_) {
343 0         0 delete $list->{$file};
344             }
345             }
346              
347 0     0   0 sub list_neg {
348 0         0 my $list = shift;
349 0         0 my %keep = map {($_, 1)} @_;
  0         0  
350 0         0 for my $file (keys %$list) {
351 0 0       0 delete $list->{$file}
352             unless defined $keep{$file};
353             }
354             }
355              
356 0     0   0 sub tags_for_file {
357 0         0 my $query = shift;
358 0         0 my $index = $self->index;
359 0         0 my @set;
360 0         0 for my $tag (sort keys %$index) {
361 0         0 for my $file (keys %{$index->{$tag}}) {
  0         0  
362 0 0       0 push @set, $tag
363             if $file eq $query;
364             }
365             }
366 0         0 return @set;
367             }
368              
369 3     3   20 sub read {
370 3         6 my $index = {};
371 3         65 my $file = $self->file;
372 3 100 66     133 return $index
373             unless -f $file and
374             open INDEX, $file;
375 2         37 while (my $line = ) {
376 16 100       60 next if $line =~ /^#/;
377 6         10 chomp $line;
378 6         14 my ($tag, $file, $comment) = split /\s+/, $line, 3;
379 6   50     24 $comment ||= '';
380 6         39 $index->{$tag}{$file} = $comment;
381             }
382 2         19 close INDEX;
383 2         10 return $index;
384             }
385              
386 3     3   4 sub write {
387 3         66 my $index = $self->index;
388 3         77 my $file = $self->file;
389 3         22 $self->assert_path($file);
390 3 50       303 open INDEX, "> $file"
391             or die "Can't open $file for output:\n$!";
392 3         11 print INDEX $self->preamble;
393 3         17 for my $tag (sort keys %$index) {
394 8         12 my $files = $index->{$tag};
395 8         19 for my $file (sort keys %$files) {
396 12         20 my $comment = $files->{$file};
397 12         18 print INDEX "$tag $file";
398 12 50       25 print INDEX "\t$comment"
399             if $comment;
400 12         29 print INDEX "\n";
401             }
402             }
403 3         10 print INDEX $self->postamble;
404 3         114 close INDEX;
405 3         80 $self->index(undef);
406             }
407              
408 3     3   5 sub preamble {
409 3         24 return <<'_';
410             # This file is an index for the `test-less` facility.
411             #
412             # More information can be found at:
413             # http://search.cpan.org/search?query=Test-Less;mode=dist
414             #
415             _
416             }
417              
418 3     3   4 sub postamble {
419 3         7 '';
420             }
421              
422 3     3   4 sub assert_path {
423 3         6 my $file = shift;
424 3 100       35 return if -e $file;
425 1 50       7 return unless $file =~ /(.+)[\\\/]/;
426 1 50       12 my $dir = $1 or return;
427 1 50       10 return if -d $dir;
428 1         134 mkdir $dir;
429             }
430              
431             __DATA__