File Coverage

blib/lib/TAP/Filter.pm
Criterion Covered Total %
statement 75 76 98.6
branch 17 18 94.4
condition 11 12 91.6
subroutine 19 19 100.0
pod 4 4 100.0
total 126 129 97.6


line stmt bran cond sub pod time code
1             package TAP::Filter;
2              
3 7     7   371630 use warnings;
  6         15  
  6         642  
4 6     6   35 use strict;
  6         9  
  6         176  
5 5     5   26 use Carp qw( confess croak );
  5         12  
  5         346  
6 5     5   5690 use TAP::Filter::Iterator;
  5         10  
  5         147  
7              
8 5     5   27 use base qw( TAP::Harness );
  5         10  
  5         5215  
9              
10             =head1 NAME
11              
12             TAP::Filter - Filter TAP stream within TAP::Harness
13              
14             =head1 VERSION
15              
16             This document describes TAP::Filter version 0.04
17              
18             =cut
19              
20             our $VERSION = '0.04';
21              
22             =head1 SYNOPSIS
23              
24             In a program:
25              
26             use TAP::Filter qw( MyFilter );
27              
28             my $harness = TAP::Filter->new;
29             $harness->runtests( @tests );
30              
31             With prove:
32              
33             prove --harness=TAP::Filter=MyFilter -rb t
34              
35             =head1 DESCRIPTION
36              
37             C allows arbitrary filters to be placed in the TAP
38             processing pipeline of L. Installed filters see the parsed
39             TAP stream a line at a time and can modify the stream by
40              
41             =over
42              
43             =item * replacing a result
44              
45             =item * injecting extra results
46              
47             =item * removing results
48              
49             =back
50              
51             C exists mainly to load a number of filters into the TAP
52             processing pipeline. Filters are generally subclasses of
53             L. See the documentation for that module for
54             information about writing filters.
55              
56             =head2 Loading filters
57              
58             Filters may be installed into the TAP processing pipeline in a number of
59             different ways...
60              
61             =head3 From the prove command line
62              
63             The C command that is supplied with L allows tests
64             to be run interactively from the command line. By default it will use
65             L to run these tests it can be told to use a different
66             harness. C (which is a subclass of L) may be
67             used with C in this way:
68              
69             prove --harness=TAP::Filter=MyFilter,OtherFilter -rb t
70              
71             C will attempt to load two filters, C and
72             C. If the name of the filter class to be loaded starts with
73             C that prefix may be omitted, so the example above would
74             load filter classes called C and
75             C.
76              
77             =head3 C<< use TAP::Filter qw( MyFilter ) >>
78              
79             If you are writing a program that uses L you can load
80             filters by replacing
81              
82             use TAP::Harness;
83             my $harness = TAP::Harness->new;
84              
85             with
86              
87             use TAP::Filter qw( MyFilter OtherFilter );
88             my $harness = TAP::Filter->new;
89              
90             As with the prove command line invocation above C will
91             attempt to load the specified filter classes from the C
92             namespace. If that fails the classnames are taken to be absolute.
93              
94             =head3 Calling C<< TAP::Filter->add_filter >>
95              
96             As an alternative to the concise filter loading notation above filters
97             may be loaded by calling C:
98              
99             use TAP::Filter;
100             TAP::Filter->add_filter( 'MyFilter' );
101             TAP::Filter->add_filter( 'OtherFilter' );
102             my $harness = TAP::Filter->new;
103              
104             Multiple filters may be loaded with a single call to C:
105              
106             TAP::Filter->add_filter( 'MyFilter', 'OtherFilter' );
107              
108             You may also pass a reference to a filter instance:
109              
110             my $my_filter = TAP::Filter::MyFilter->new;
111             TAP::Filter->add_filter( $my_filter );
112              
113             =head2 Filter scope
114              
115             C maintains a single, global list of installed filters.
116             Once loaded filters can not be removed. If either of these features
117             proves problematic let me know and I'll consider alternatives.
118              
119             =head1 INTERFACE
120              
121             =head2 C<< add_filter >>
122              
123             Add one or more filters to C's filter chain. Each argument
124             to C may be either
125              
126             =over
127              
128             =item * a partial class name
129              
130             =item * a complete class name
131              
132             =item * a filter instance
133              
134             =back
135              
136             If the filter's class name begins with C it is only
137             necessary to supply the trailing portion of the name:
138              
139             # Looks for TAP::Filter::Foo::Bar then plain Foo::Bar
140             TAP::Filter->add_filter( 'Foo::Bar' );
141              
142             =cut
143              
144             sub _filter_from_class_name {
145 10     10   19 my $class = shift;
146 10         68 my $name = shift;
147 10 100 100     85 return $name if !defined $name || ref $name;
148 5         12 my @err = ();
149 5         11 for my $prefix ( 'TAP::Filter::', '' ) {
150 9         17 my $class_name = $prefix . $name;
151 9     5   649 eval "use $class_name";
  5     3   1293  
  2         4  
  2         59  
  3         1303  
  2         6272  
  2         63  
152 9 100       54 return $class_name->new unless $@;
153 5         20 push @err, $@;
154             }
155 1         246 croak "Can't load filter class for $name\n", join( '', @err );
156             }
157              
158             {
159             my @Filter = ();
160              
161             sub add_filter {
162 14     14 1 2867 my $class = shift;
163 14         146 for my $name ( @_ ) {
164 10         33 my $filter = $class->_filter_from_class_name( $name );
165 9 100 100     11752 croak "Filter must have a 'add_to_parser' method"
      100        
166             unless defined $filter
167             && UNIVERSAL::can( $filter, 'can' )
168             && $filter->can( 'add_to_parser' );
169 5         718 push @Filter, $filter;
170             }
171             }
172              
173             =head2 C<< get_filters >>
174              
175             Returns a list of currently installed filters. Each item in the list
176             will be a reference to an instantiated filter - even if the
177             corresponding filter was specified by class name.
178              
179             =cut
180              
181             sub get_filters {
182 8     8 1 15679 my $class = shift;
183 8         76 return @Filter;
184             }
185             }
186              
187             =head2 C<< make_parser >>
188              
189             Subclassed from C. Create a new C and install
190             any registered filters in its TAP processing pipeline.
191              
192             Study the implementation of C if you need to implement an
193             alternative filter loading scheme.
194              
195             =cut
196              
197             sub make_parser {
198 3     3 1 226884 my ( $self, @args ) = @_;
199              
200 3         30 my ( $parser, $session ) = $self->SUPER::make_parser( @args );
201              
202 3         71963 for my $filter ( reverse $self->get_filters ) {
203 9         88 $filter->add_to_parser( $parser );
204             }
205              
206 3         35 return ( $parser, $session );
207             }
208              
209             sub import {
210 6     6   52 my $class = shift;
211 6         30 $class->add_filter( @_ );
212             }
213              
214             =head2 C<< ok >>
215              
216             A convenience method for creating new test results to inject into the
217             TAP stream.
218              
219             my $result = TAP::Filter->ok(
220             ok => 1, # test passed
221             description => 'A test',
222             );
223              
224             The returned result is an instance of L suitable
225             for feeding into the TAP stream. See L for more
226             information about manipulating the TAP stream.
227              
228             The arguments to C are a number of key, value pairs. The following
229             keys are recognised:
230              
231             =over
232              
233             =item C
234              
235             Boolean. Whether the test passed.
236              
237             =item C
238              
239             The textual description of the test.
240              
241             =item C
242              
243             A TODO or SKIP directive.
244              
245             =item C
246              
247             Text explaining why the test is a skip or todo.
248              
249             =back
250              
251             =cut
252              
253             sub _load_result_maker {
254 2     2   11 my @classes = (
255             [ 'TAP::Parser::ResultFactory' => 'make_result' ],
256             [ 'TAP::Parser::Result' => 'new' ]
257             );
258              
259 2         8 for my $ctor ( @classes ) {
260 2         6 my ( $pkg, $method ) = @$ctor;
261 2         232 eval "use $pkg ()";
262 2 50       11 unless ( $@ ) {
263             return sub {
264 13     13   83 return $pkg->$method( @_ );
265 2         21 };
266             }
267             }
268              
269 0         0 confess "Can't load a suitable TAP::Parser::Result"
270             . " factory class, tried ", join( ', ', @classes ), "\n";
271             }
272              
273             {
274             my $result_maker = undef;
275 13   66 13   62 sub _result_maker { $result_maker ||= _load_result_maker }
276             }
277              
278             sub _trim {
279 26     26   49 my $data = shift;
280 26 100       100 return '' unless defined $data;
281 10         26 $data =~ s/^\s+//;
282 10         21 $data =~ s/\s+$//;
283 10         25 return $data;
284             }
285              
286             sub _escape {
287 10     10   14 my $str = shift;
288 10         20 $str =~ s/([#\\])/\\$1/g;
289 10         24 return $str;
290             }
291              
292             sub _make_raw {
293 13     13   18 my $spec = shift;
294 13         36 my @raw = ( $spec->{ok}, '*' );
295 13 100       47 push @raw, _escape( $spec->{description} )
296             if $spec->{description};
297 13 100       39 if ( my $dir = $spec->{directive} ) {
298 4         10 push @raw, "# $dir";
299 4 100       12 push @raw, _escape( $spec->{explanation} )
300             if $spec->{explanation};
301             }
302 13         60 return join ' ', @raw;
303             }
304              
305             {
306             my %spec_filter = (
307             type => sub { 'test' },
308             test_num => sub { 0 },
309             explanation => sub { _trim( shift ) },
310             description => sub {
311             my $desc = _trim( shift );
312             return $desc ? "- $desc" : '';
313             },
314             directive => sub {
315             my $dir = shift;
316             return uc( defined $dir ? $dir : '' );
317             },
318             ok => sub { $_[0] ? 'ok' : 'not ok' },
319             );
320              
321             sub ok {
322 14     14 1 20551 my $class = shift;
323 14 100       303 croak "ok needs a number of name => value pairs"
324             if @_ & 1;
325 13         83 my %spec = @_;
326              
327 13         50 for my $name ( keys %spec_filter ) {
328 78         242 $spec{$name} = $spec_filter{$name}->( $spec{$name} );
329             }
330              
331 13         46 $spec{raw} = _make_raw( \%spec );
332 13         39 return _result_maker()->( \%spec );
333             }
334             }
335              
336             1;
337             __END__