File Coverage

blib/lib/WARC/Record/Replay.pm
Criterion Covered Total %
statement 73 73 100.0
branch 30 34 88.2
condition 9 9 100.0
subroutine 8 8 100.0
pod 2 2 100.0
total 122 126 96.8


line stmt bran cond sub pod time code
1             package WARC::Record::Replay; # -*- CPerl -*-
2              
3 26     26   72988 use strict;
  26         91  
  26         785  
4 26     26   130 use warnings;
  26         47  
  26         613  
5              
6 26     26   577 use WARC; *WARC::Record::Replay::VERSION = \$WARC::VERSION;
  26         64  
  26         862  
7              
8             =head1 NAME
9              
10             WARC::Record::Replay - WARC record replay registry and autoloading
11              
12             =head1 SYNOPSIS
13              
14             use WARC::Record;
15              
16             $object = $record->replay;
17              
18             =cut
19              
20 26     26   136 use Carp;
  26         67  
  26         1450  
21 26     26   156 use File::Spec;
  26         67  
  26         26686  
22              
23             # array of arrays:
24             # ( [ predicate coderef, handler coderef ]... )
25             our @Handlers = ();
26             # Each predicate is called with the record object locally stored in $_ and
27             # must return false to reject the record or true to accept the record.
28             #
29             # Each handler for which the predicate returns true is tried in order.
30              
31             =head1 DESCRIPTION
32              
33             This is an internal module that provides a registry of protocol replay
34             support modules and an autoloading facility.
35              
36             =over
37              
38             =cut
39              
40             # Scan @INC for autoload descriptors and see if any available modules can
41             # be autoloaded for this record.
42              
43             sub _try_autoload_for ($) {
44 24     24   40 my $record = shift;
45 24         43 my $loaded = 0;
46              
47 24         48 local *_;
48 24         45 foreach my $area (@INC) {
49 218         612 my $vol; my $dirpath; my $tail;
  218         0  
50 218         2414 ($vol, $dirpath, $tail) = File::Spec->splitpath($area);
51 218         1037 my @dirs = File::Spec->splitdir($dirpath);
52 218         1950 my $dirname = File::Spec->catpath
53             ($vol, File::Spec->catdir(@dirs, $tail, qw/WARC Record Replay/));
54              
55 218 100       3932 next unless -d $dirname;
56              
57 62 50       1982 opendir my $dir, $dirname or die "autoload dirscan $dirname: $!";
58 62         1512 my @modules = grep defined, map {/^([[:alnum:]_]+[.]pm)$/; $1} # untaint
  82         352  
  82         377  
59             grep /[.]pm$/, readdir $dir;
60 62 50       772 closedir $dir or die "autoload dirscan close $dirname: $!";
61              
62             FILE:
63 62         185 foreach my $module (@modules) {
64 78         1202 my $filename = File::Spec->catpath
65             ($vol, File::Spec->catdir(@dirs, $tail, qw/WARC Record Replay/),
66             $module);
67 78         465 my $modfilename = File::Spec::Unix->catfile
68             (qw/WARC Record Replay/, $module);
69 78 100       308 next FILE if $INC{$modfilename};
70              
71 63 50       2468 open my $file, '<', $filename or die "autoload scan $filename: $!";
72 63         191 my $descriptor_found = 0;
73             LINE:
74 63         835 while (<$file>) {
75 717 100       1648 if (m/^=(?:for|begin)\s+autoload(?:\s+|$)/)
76 63         106 { $descriptor_found = 1; next LINE; }
  63         197  
77 654 100       1380 next LINE unless $descriptor_found;
78 247 100       532 last LINE if m/^=/;
79              
80 192 100       404 if (m/^\[WARC::Record::Replay\]$/)
81 63         105 { $descriptor_found = 2; next LINE; }
  63         163  
82 129 100       237 next LINE if $descriptor_found < 2;
83 121 100       217 last LINE if m/^\[/;
84              
85             # ... parse and test conditional; load if matched
86 118 100       428 if (m/^([[:alpha:]][_[:alnum:]]*)\(([-_[:alnum:]]*)\)\s*=\s*(.+)$/) {
87             # $1: method $2: argument $3: match
88 63         91 my $match_valid = 0; my $match_value;
  63         85  
89 63         97 eval {$match_value = $record->$1($2); $match_valid = 1};
  63         352  
  59         180  
90 63 100 100     650 if ($match_valid and $match_value =~ $3)
91 5         2138 { require $modfilename; $loaded++; last LINE }
  4         123  
  4         13  
92             }
93             }
94 62 50       1269 close $file or die "autoload scan close $file: $!";
95             }
96             }
97              
98 23         144 return $loaded
99             }
100              
101             =item WARC::Record::Replay::register { predicate } $handler
102              
103             Add a handler to the internal list of replay handlers. The given handler
104             will be used for records on which the given predicate returns true.
105              
106             The predicate will be evaluated with $_ locally set to the record object to
107             be replayed and @_ empty each time a record is replayed.
108              
109             =cut
110              
111             sub register (&$) {
112 21 100 100 21 1 3591 croak "attempt to register invalid handler"
113             unless (ref $_[0] eq 'CODE') && (ref $_[1] eq 'CODE');
114              
115 19         55 push @Handlers, [ @_[0, 1] ];
116              
117             return # nothing
118 19         42 }
119              
120             =item WARC::Record::Replay::find_handlers( $record )
121              
122             Return a list of handlers that can replay the protocol message in $record.
123              
124             =cut
125              
126             sub find_handlers ($) {
127 150     150 1 4594 my $record = shift;
128 150         578 my @handlers = ();
129              
130             {
131 150         218 local *_; $_ = $record;
  150         364  
  150         228  
132 150         339 foreach my $handler (@Handlers)
133 506 100       1322 { push @handlers, $handler->[1] if $handler->[0]->() }
134             }
135              
136 150 100 100     519 if (scalar @handlers == 0 and _try_autoload_for $record)
137             # repeat the search now that a module has been loaded
138 4         13 { unshift @_, $record; goto &find_handlers }
  4         19  
139              
140             return @handlers
141 145         424 }
142              
143             =back
144              
145             =cut
146              
147             1;
148             __END__