File Coverage

blib/lib/CPAN/Reporter/History.pm
Criterion Covered Total %
statement 118 121 97.5
branch 34 42 80.9
condition 11 17 64.7
subroutine 24 24 100.0
pod 1 1 100.0
total 188 205 91.7


line stmt bran cond sub pod time code
1 36     36   6326 use strict;
  36         39  
  36         1502  
2             package CPAN::Reporter::History;
3              
4             our $VERSION = '1.2018';
5              
6 36     36   128 use vars qw/@ISA @EXPORT_OK/;
  36         40  
  36         1525  
7              
8 36     36   120 use Config;
  36         40  
  36         986  
9 36     36   108 use Carp;
  36         45  
  36         1576  
10 36     36   124 use Fcntl qw/:flock/;
  36         41  
  36         3808  
11 36     36   142 use File::HomeDir ();
  36         44  
  36         534  
12 36     36   98 use File::Path (qw/mkpath/);
  36         38  
  36         1257  
13 36     36   127 use File::Spec ();
  36         38  
  36         404  
14 36     36   848 use IO::File ();
  36         1348  
  36         414  
15 36     36   93 use CPAN (); # for printing warnings
  36         37  
  36         408  
16 36     36   940 use CPAN::Reporter::Config ();
  36         59  
  36         3460  
17              
18             require Exporter;
19             @ISA = qw/Exporter/;
20             @EXPORT_OK = qw/have_tested/;
21              
22             #--------------------------------------------------------------------------#
23             # Some platforms don't implement flock, so fake it if necessary
24             #--------------------------------------------------------------------------#
25              
26             BEGIN {
27 36     36   52 eval {
28 36         1304 my $temp_file = File::Spec->catfile(
29             File::Spec->tmpdir(), $$ . time()
30             );
31 36         242 my $fh = IO::File->new( $temp_file, "w" );
32 36         6663 flock $fh, LOCK_EX;
33 36         262 $fh->close;
34 36         2260 unlink $temp_file;
35             };
36 36 50       39830 if ( $@ ) {
37 0         0 *CORE::GLOBAL::flock = sub (*$) { 1 };
  0         0  
38             }
39             }
40              
41             #--------------------------------------------------------------------------#
42             # Back-compatibility checks -- just once per load
43             #--------------------------------------------------------------------------#
44              
45              
46             # 0.99_08 changed the history file format and name
47             # If an old file exists, convert it to the new name and format. Note --
48             # someone running multiple installations of CPAN::Reporter might have old
49             # and new versions running so only convert in the case where the old file
50             # exists and the new file does not
51              
52             {
53             my $old_history_file = _get_old_history_file();
54             my $new_history_file = _get_history_file();
55             last if -f $new_history_file || ! -f $old_history_file;
56              
57             $CPAN::Frontend->mywarn("CPAN::Reporter: Your history file is in an old format. Upgrading automatically.\n");
58              
59             # open old and new files
60             my ($old_fh, $new_fh);
61             if (! ( $old_fh = IO::File->new( $old_history_file ) ) ) {
62             $CPAN::Frontend->mywarn("CPAN::Reporter: error opening old history file: $!\nContinuing without conversion.\n");
63             last;
64             }
65             if (! ($new_fh = IO::File->new( $new_history_file, "w" ) ) ) {
66             $CPAN::Frontend->mywarn("CPAN::Reporter: error opening new history file: $!\nContinuing without conversion.\n");
67             last;
68             }
69              
70             print {$new_fh} _generated_by();
71             while ( my $line = <$old_fh> ) {
72             chomp $line;
73             # strip off perl version and convert
74             # try not to match 5.1 from "MSWin32-x86-multi-thread 5.1"
75             # from really old CPAN::Reporter history formats
76             my ($old_version, $perl_patch);
77             if ( $line =~ m{ (5\.0\d{2,5}) ?(patch \d+)?\z} ) {
78             ($old_version, $perl_patch) = ($1, $2);
79             $line =~ s{ (5\.0\d{2,5}) ?(patch \d+)?\z}{};
80             }
81             my $pv = $old_version ? "perl-" . _perl_version($old_version)
82             : "unknown";
83             $pv .= " $perl_patch" if $perl_patch;
84             my ($grade_dist, $arch_os) = ($line =~ /(\S+ \S+) (.+)/);
85             print {$new_fh} "test $grade_dist ($pv) $arch_os\n";
86             }
87             close $old_fh;
88             close $new_fh;
89             }
90              
91              
92             #--------------------------------------------------------------------------#
93             # Public methods
94             #--------------------------------------------------------------------------#
95              
96             #--------------------------------------------------------------------------#
97             # have_tested -- search for dist in history file
98             #--------------------------------------------------------------------------#
99              
100             sub have_tested { ## no critic RequireArgUnpacking
101             # validate arguments
102 40 100   40 1 2081973 croak "arguments to have_tested() must be key value pairs"
103             if @_ % 2;
104              
105 39         111 my $args = { @_ };
106              
107             my @bad_params = grep {
108 39         163 $_ !~ m{^(?:dist|phase|grade|perl|archname|osvers)$} } keys %$args;
  60         237  
109 39 100       170 croak "bad parameters for have_tested(): " . join(q{, },@bad_params)
110             if @bad_params;
111              
112              
113             # DWIM: grades to upper case
114 38 100       163 $args->{grade} = uc $args->{grade} if defined $args->{grade};
115              
116             # default to current platform
117 38 100       109 $args->{perl} = _format_perl_version() unless defined $args->{perl};
118 38 100       187 $args->{archname} = $Config{archname} unless defined $args->{archname};
119 38 100       205 $args->{osvers} = $Config{osvers} unless defined $args->{osvers};
120              
121 38         47 my @found;
122 38 50       58 my $history = _open_history_file('<') or return;
123 38         196 flock $history, LOCK_SH;
124 38         338 <$history>; # throw away format line
125 38         131 while ( defined (my $line = <$history>) ) {
126 429 50       597 my $fields = _split_history( $line ) or next;
127 429 100       449 push @found, $fields if _match($fields, $args);
128             }
129 38         98 $history->close;
130 38         594 return @found;
131             }
132              
133             #--------------------------------------------------------------------------#
134             # Private methods
135             #--------------------------------------------------------------------------#
136              
137             #--------------------------------------------------------------------------#
138             # _format_history --
139             #
140             # phase grade dist-version (perl-version patchlevel) archname osvers
141             #--------------------------------------------------------------------------#
142              
143             sub _format_history {
144 216     216   264 my ($result) = @_;
145 216         431 my $phase = $result->{phase};
146 216         531 my $grade = uc $result->{grade};
147 216         323 my $dist_name = $result->{dist_name};
148 216         418 my $perlver = "perl-" . _format_perl_version();
149 216         1953 my $platform = "$Config{archname} $Config{osvers}";
150 216         950 return "$phase $grade $dist_name ($perlver) $platform\n";
151             }
152              
153             #--------------------------------------------------------------------------#
154             # _format_perl_version
155             #--------------------------------------------------------------------------#
156              
157             sub _format_perl_version {
158 417     417   1487 my $pv = _perl_version();
159             $pv .= " patch $Config{perl_patchlevel}"
160 417 50       4688 if $Config{perl_patchlevel};
161 417         1587 return $pv;
162             }
163              
164             sub _generated_by {
165 21     21   203 return "# Generated by CPAN::Reporter "
166             . "$CPAN::Reporter::History::VERSION\n";
167             }
168              
169             #--------------------------------------------------------------------------#
170             # _get_history_file
171             #--------------------------------------------------------------------------#
172              
173             sub _get_history_file {
174 289     289   838 return File::Spec->catdir(
175             CPAN::Reporter::Config::_get_config_dir(), "reports-sent.db"
176             );
177             }
178              
179             #--------------------------------------------------------------------------#
180             # _get_old_history_file -- prior to 0.99_08
181             #--------------------------------------------------------------------------#
182              
183             sub _get_old_history_file {
184 36     36   112 return File::Spec->catdir(
185             CPAN::Reporter::Config::_get_config_dir(), "history.db"
186             );
187             }
188              
189             #--------------------------------------------------------------------------#
190             # _is_duplicate
191             #--------------------------------------------------------------------------#
192              
193             sub _is_duplicate {
194 155     155   292 my ($result) = @_;
195 155         613 my $log_line = _format_history( $result );
196 155 100       477 my $history = _open_history_file('<') or return;
197 133         278 my $found = 0;
198 133         860 flock $history, LOCK_SH;
199 133         1777 while ( defined (my $line = <$history>) ) {
200 373 100       1008 if ( $line eq $log_line ) {
201 98         216 $found++;
202 98         1771 last;
203             }
204             }
205 132         743 $history->close;
206 132         1907 return $found;
207             }
208              
209             #--------------------------------------------------------------------------#
210             # _match
211             #--------------------------------------------------------------------------#
212              
213             sub _match {
214 428     429   304 my ($fields, $search) = @_;
215 428         618 for my $k ( keys %$search ) {
216 878 100       1052 next if $search->{$k} eq q{}; # empty string matches anything
217 824 100       2024 return unless $fields->{$k} eq $search->{$k};
218             }
219 87         338 return 1; # all keys matched
220             }
221              
222             #--------------------------------------------------------------------------#
223             # _open_history_file
224             #--------------------------------------------------------------------------#
225              
226             sub _open_history_file {
227 253   50 254   720 my $mode = shift || '<';
228 253         470 my $history_filename = _get_history_file();
229 253         3730 my $file_exists = -f $history_filename;
230              
231             # shortcut if reading and doesn't exist
232 253 100 100     1490 return if ( $mode eq '<' && ! $file_exists );
233              
234             # open it in the desired mode
235 231 50       1994 my $history = IO::File->new( $history_filename, $mode )
236             or $CPAN::Frontend->mywarn("CPAN::Reporter: couldn't open history file "
237             . "'$history_filename': $!\n");
238              
239             # if writing and it didn't exist before, initialize with header
240 231 100 100     21115 if ( substr($mode,0,1) eq '>' && ! $file_exists ) {
241 20         28 print {$history} _generated_by();
  20         69  
242             }
243              
244 231         719 return $history;
245             }
246              
247             #--------------------------------------------------------------------------#
248             # _perl_version
249             #--------------------------------------------------------------------------#
250              
251             sub _perl_version {
252 423   33 424   30867 my $ver = shift || "$]";
253 423         5520 $ver =~ qr/(\d)\.(\d{3})(\d{0,3})/;
254 423   50     4264 my ($maj,$min,$pat) = (0 + ($1||0), 0 + ($2||0), 0 + ($3||0));
      50        
      50        
255 423         411 my $pv;
256 423 50       838 if ( $min < 6 ) {
257 0         0 $pv = $ver;
258             }
259             else {
260 423         1017 $pv = "$maj\.$min\.$pat";
261             }
262 423         805 return $pv;
263             }
264              
265             #--------------------------------------------------------------------------#
266             # _record_history
267             #--------------------------------------------------------------------------#
268              
269             sub _record_history {
270 61     62   414 my ($result) = @_;
271 61         114 my $log_line = _format_history( $result );
272 61 50       124 my $history = _open_history_file('>>') or return;
273              
274 61         760 flock( $history, LOCK_EX );
275 61         138 seek( $history, 0, 2 ); # seek to end of file
276 61         259 $history->print( $log_line );
277 61         1079 flock( $history, LOCK_UN );
278              
279 61         206 $history->close;
280 61         669 return;
281             }
282              
283             #--------------------------------------------------------------------------#
284             # _split_history
285             #
286             # splits lines created with _format_history. Returns hash ref with
287             # phase, grade, dist, perl, platform
288             #--------------------------------------------------------------------------#
289              
290             sub _split_history {
291 428     429   369 my ($line) = @_;
292 428         325 chomp $line;
293 428         245 my %fields;
294 428         1934 @fields{qw/phase grade dist perl archname osvers/} =
295             $line =~ m{
296             ^(\S+) \s+ # phase
297             (\S+) \s+ # grade
298             (\S+) \s+ # dist
299             \(perl- ([^)]+) \) \s+ # (perl-version-patchlevel)
300             (\S+) \s+ # archname
301             (.+)$ # osvers
302             }xms;
303              
304             # return nothing if parse fails
305 428 50       685 return if scalar keys %fields == 0;# grep { ! defined($_) } values %fields;
306             # otherwise return hashref
307 428         666 return \%fields;
308             }
309              
310             1;
311              
312             # ABSTRACT: Read or write a CPAN::Reporter history log
313              
314             __END__