File Coverage

blib/lib/TAP/Parser/IteratorFactory.pm
Criterion Covered Total %
statement 88 95 92.6
branch 27 34 79.4
condition 11 17 64.7
subroutine 17 17 100.0
pod 5 5 100.0
total 148 168 88.1


line stmt bran cond sub pod time code
1             package TAP::Parser::IteratorFactory;
2              
3 38     38   1185 use strict;
  38         94  
  38         1188  
4 38     38   279 use warnings;
  38         105  
  38         1307  
5              
6 38     38   254 use Carp qw( confess );
  38         100  
  38         2678  
7 38     38   273 use File::Basename qw( fileparse );
  38         99  
  38         3279  
8              
9 38     38   269 use base 'TAP::Object';
  38         135  
  38         3254  
10              
11 38     38   291 use constant handlers => [];
  38         102  
  38         60995  
12              
13             =head1 NAME
14              
15             TAP::Parser::IteratorFactory - Figures out which SourceHandler objects to use for a given Source
16              
17             =head1 VERSION
18              
19             Version 3.40_01
20              
21             =cut
22              
23             our $VERSION = '3.40_01';
24              
25             =head1 SYNOPSIS
26              
27             use TAP::Parser::IteratorFactory;
28             my $factory = TAP::Parser::IteratorFactory->new({ %config });
29             my $iterator = $factory->make_iterator( $filename );
30              
31             =head1 DESCRIPTION
32              
33             This is a factory class that takes a L and runs it through all the
34             registered Ls to see which one should handle the source.
35              
36             If you're a plugin author, you'll be interested in how to Ls,
37             how L works.
38              
39             =head1 METHODS
40              
41             =head2 Class Methods
42              
43             =head3 C
44              
45             Creates a new factory class:
46              
47             my $sf = TAP::Parser::IteratorFactory->new( $config );
48              
49             C<$config> is optional. If given, sets L and calls L.
50              
51             =cut
52              
53             sub _initialize {
54 307     307   1167 my ( $self, $config ) = @_;
55 307   100     2990 $self->config( $config || {} )->load_handlers;
56 307         1335 return $self;
57             }
58              
59             =head3 C
60              
61             Registers a new L with this factory.
62              
63             __PACKAGE__->register_handler( $handler_class );
64              
65             =head3 C
66              
67             List of handlers that have been registered.
68              
69             =cut
70              
71             sub register_handler {
72 179     179 1 7650 my ( $class, $dclass ) = @_;
73              
74 179 50 33     2553 confess("$dclass must implement can_handle & make_iterator methods!")
75             unless UNIVERSAL::can( $dclass, 'can_handle' )
76             && UNIVERSAL::can( $dclass, 'make_iterator' );
77              
78 179         1116 my $handlers = $class->handlers;
79 179         570 push @{$handlers}, $dclass
80 179 50       414 unless grep { $_ eq $dclass } @{$handlers};
  356         1270  
  179         648  
81              
82 179         676 return $class;
83             }
84              
85             ##############################################################################
86              
87             =head2 Instance Methods
88              
89             =head3 C
90              
91             my $cfg = $sf->config;
92             $sf->config({ Perl => { %config } });
93              
94             Chaining getter/setter for the configuration of the available source handlers.
95             This is a hashref keyed on handler class whose values contain config to be passed
96             onto the handlers during detection & creation. Class names may be fully qualified
97             or abbreviated, eg:
98              
99             # these are equivalent
100             $sf->config({ 'TAP::Parser::SourceHandler::Perl' => { %config } });
101             $sf->config({ 'Perl' => { %config } });
102              
103             =cut
104              
105             sub config {
106 923     923 1 6623 my $self = shift;
107 923 100       4882 return $self->{config} unless @_;
108 309 100       1489 unless ( 'HASH' eq ref $_[0] ) {
109 1         13 $self->_croak('Argument to &config must be a hash reference');
110             }
111 308         1157 $self->{config} = shift;
112 308         2154 return $self;
113             }
114              
115             sub _last_handler {
116 316     316   6100 my $self = shift;
117 316 100       2096 return $self->{last_handler} unless @_;
118 304         1047 $self->{last_handler} = shift;
119 304         738 return $self;
120             }
121              
122             sub _testing {
123 316     316   923 my $self = shift;
124 316 100       1719 return $self->{testing} unless @_;
125 12         25 $self->{testing} = shift;
126 12         29 return $self;
127             }
128              
129             ##############################################################################
130              
131             =head3 C
132              
133             $sf->load_handlers;
134              
135             Loads the handler classes defined in L. For example, given a config:
136              
137             $sf->config({
138             MySourceHandler => { some => 'config' },
139             });
140              
141             C will attempt to load the C class by looking in
142             C<@INC> for it in this order:
143              
144             TAP::Parser::SourceHandler::MySourceHandler
145             MySourceHandler
146              
147             Cs on error.
148              
149             =cut
150              
151             sub load_handlers {
152 307     307 1 1044 my ($self) = @_;
153 307         737 for my $handler ( keys %{ $self->config } ) {
  307         1137  
154 5         30 my $sclass = $self->_load_handler($handler);
155              
156             # TODO: store which class we loaded anywhere?
157             }
158 307         951 return $self;
159             }
160              
161             sub _load_handler {
162 5     5   168 my ( $self, $handler ) = @_;
163              
164 5         19 my @errors;
165 5         31 for my $dclass ( "TAP::Parser::SourceHandler::$handler", $handler ) {
166 8 100 66     148 return $dclass
167             if UNIVERSAL::can( $dclass, 'can_handle' )
168             && UNIVERSAL::can( $dclass, 'make_iterator' );
169              
170 5     3   1002 eval "use $dclass";
  3     2   1330  
  0         0  
  0         0  
  2         1252  
  2         100  
  2         52  
171 5 100       42 if ( my $e = $@ ) {
172 3         14 push @errors, $e;
173 3         14 next;
174             }
175              
176 2 50 33     56 return $dclass
177             if UNIVERSAL::can( $dclass, 'can_handle' )
178             && UNIVERSAL::can( $dclass, 'make_iterator' );
179 0         0 push @errors,
180             "handler '$dclass' does not implement can_handle & make_iterator";
181             }
182              
183             $self->_croak(
184 0         0 "Cannot load handler '$handler': " . join( "\n", @errors ) );
185             }
186              
187             ##############################################################################
188              
189             =head3 C
190              
191             my $iterator = $src_factory->make_iterator( $source );
192              
193             Given a L, finds the most suitable L
194             to use to create a L (see L). Dies on error.
195              
196             =cut
197              
198             sub make_iterator {
199 307     307 1 1157 my ( $self, $source ) = @_;
200              
201 307 50       1370 $self->_croak('no raw source defined!') unless defined $source->raw;
202              
203 307         1214 $source->config( $self->config )->assemble_meta;
204              
205             # is the raw source already an object?
206             return $source->raw
207             if ( $source->meta->{is_object}
208 307 50 66     1298 && UNIVERSAL::isa( $source->raw, 'TAP::Parser::SourceHandler' ) );
209              
210             # figure out what kind of source it is
211 307         1742 my $sd_class = $self->detect_source($source);
212 304         1671 $self->_last_handler($sd_class);
213              
214 304 100       1340 return if $self->_testing;
215              
216             # create it
217 293         1892 my $iterator = $sd_class->make_iterator($source);
218              
219 293         4963 return $iterator;
220             }
221              
222             =head3 C
223              
224             Given a L, detects what kind of source it is and
225             returns I L (the most confident one). Dies
226             on error.
227              
228             The detection algorithm works something like this:
229              
230             for (@registered_handlers) {
231             # ask them how confident they are about handling this source
232             $confidence{$handler} = $handler->can_handle( $source )
233             }
234             # choose the most confident handler
235              
236             Ties are handled by choosing the first handler.
237              
238             =cut
239              
240             sub detect_source {
241 307     307 1 1089 my ( $self, $source ) = @_;
242              
243 307 50       1216 confess('no raw source ref defined!') unless defined $source->raw;
244              
245             # find a list of handlers that can handle this source:
246 307         896 my %confidence_for;
247 307         767 for my $handler ( @{ $self->handlers } ) {
  307         4015  
248 1545         16647 my $confidence = $handler->can_handle($source);
249             # warn "handler: $handler: $confidence\n";
250 1545 100       6338 $confidence_for{$handler} = $confidence if $confidence;
251             }
252              
253 307 100       1284 if ( !%confidence_for ) {
254             # error: can't detect source
255 2         5 my $raw_source_short = substr( ${ $source->raw }, 0, 50 );
  2         17  
256 2         452 confess("Cannot detect source of '$raw_source_short'!");
257 0         0 return;
258             }
259              
260             # if multiple handlers can handle it, choose the most confident one
261             my @handlers =
262 305         1870 sort { $confidence_for{$b} <=> $confidence_for{$a} }
  12         183  
263             keys %confidence_for;
264              
265             # Check for a tie.
266 305 100 100     1816 if( @handlers > 1 &&
267             $confidence_for{$handlers[0]} == $confidence_for{$handlers[1]}
268             ) {
269 1         4 my $filename = $source->meta->{file}{basename};
270 1         16 die("There is a tie between $handlers[0] and $handlers[1].\n".
271             "Both voted $confidence_for{$handlers[0]} on $filename.\n");
272             }
273              
274             # this is really useful for debugging handlers:
275 304 50       1356 if ( $ENV{TAP_HARNESS_SOURCE_FACTORY_VOTES} ) {
276             warn(
277             "votes: ",
278 0         0 join( ', ', map {"$_: $confidence_for{$_}"} @handlers ),
  0         0  
279             "\n"
280             );
281             }
282              
283             # return 1st
284 304         1327 return $handlers[0];
285             }
286              
287             1;
288              
289             __END__