File Coverage

blib/lib/CPAN/Reporter/History.pm
Criterion Covered Total %
statement 124 127 97.6
branch 35 46 76.0
condition 11 17 64.7
subroutine 25 25 100.0
pod 1 1 100.0
total 196 216 90.7


line stmt bran cond sub pod time code
1 36     36   11642 use strict;
  36         82  
  36         2077  
2             package CPAN::Reporter::History;
3              
4             our $VERSION = '1.2019';
5              
6 36     36   237 use vars qw/@ISA @EXPORT_OK/;
  36         80  
  36         1818  
7              
8 36     36   218 use Config;
  36         85  
  36         1298  
9 36     36   180 use Carp;
  36         85  
  36         2240  
10 36     36   242 use Fcntl qw/:flock/;
  36         79  
  36         4734  
11 36     36   272 use File::HomeDir ();
  36         87  
  36         1004  
12 36     36   229 use File::Path (qw/mkpath/);
  36         122  
  36         1978  
13 36     36   247 use File::Spec ();
  36         129  
  36         1069  
14 36     36   1317 use IO::File ();
  36         2325  
  36         678  
15 36     36   201 use CPAN (); # for printing warnings
  36         71  
  36         705  
16 36     36   1273 use CPAN::Reporter::Config ();
  36         101  
  36         5598  
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   137 eval {
28 36         1758 my $temp_file = File::Spec->catfile(
29             File::Spec->tmpdir(), $$ . time()
30             );
31 36         391 my $fh = IO::File->new( $temp_file, "w" );
32 36         9415 flock $fh, LOCK_EX;
33 36         518 $fh->close;
34 36         7129 unlink $temp_file;
35             };
36 36 50       63328 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 2173016 croak "arguments to have_tested() must be key value pairs"
103             if @_ % 2;
104              
105 39         222 my $args = { @_ };
106              
107             my @bad_params = grep {
108 39         338 $_ !~ m{^(?:dist|phase|grade|perl|archname|osvers)$} } keys %$args;
  60         601  
109 39 100       280 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       251 $args->{grade} = uc $args->{grade} if defined $args->{grade};
115              
116             # default to current platform
117 38 100       165 $args->{perl} = _format_perl_version() unless defined $args->{perl};
118 38 100       232 $args->{archname} = _format_archname() unless defined $args->{archname};
119 38 100       337 $args->{osvers} = $Config{osvers} unless defined $args->{osvers};
120              
121 38         109 my @found;
122 38 50       97 my $history = _open_history_file('<') or return;
123 38         496 flock $history, LOCK_SH;
124 38         826 <$history>; # throw away format line
125 38         326 while ( defined (my $line = <$history>) ) {
126 429 50       1059 my $fields = _split_history( $line ) or next;
127 429 100       776 push @found, $fields if _match($fields, $args);
128             }
129 38         247 $history->close;
130 38         1172 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   913 my ($result) = @_;
145 216         946 my $phase = $result->{phase};
146 216         1194 my $grade = uc $result->{grade};
147 216         797 my $dist_name = $result->{dist_name};
148 216         851 my $perlver = "perl-" . _format_perl_version();
149 216         3089 my $osvers = $Config{osvers};
150 216         1577 my $archname = _format_archname();
151 216         2450 return "$phase $grade $dist_name ($perlver) $archname $osvers\n";
152             }
153              
154             #--------------------------------------------------------------------------#
155             # _format_archname --
156             #
157             # appends info about taint being disabled to Config.pm's archname
158             #--------------------------------------------------------------------------#
159              
160             sub _format_archname {
161 256     256   2457 my $archname = $Config{archname};
162             # `taint_disabled` is correctly set as of perl-blead@da791ecc, which will
163             # be in 5.37.12 and later. Before then it is always false (indeed,
164             # non-existent) and the only way to check whether taint is disabled is to
165             # check the ccflags. Before that and its related commits (see
166             # https://github.com/Perl/perl5/pull/20983) were merged it was impossible
167             # to build a clean perl with taint support disabled that passed all its own
168             # tests.
169 256 50       19803 if($Config{taint_disabled}) {
170 1 0       5 $archname .= '-silent' if($Config{taint_disabled} eq 'silent');
171 1         33 $archname .= '-no-taint-support';
172             }
173 256         1390 return $archname;
174             }
175              
176             #--------------------------------------------------------------------------#
177             # _format_perl_version
178             #--------------------------------------------------------------------------#
179              
180             sub _format_perl_version {
181 417     417   4176 my $pv = _perl_version();
182             $pv .= " patch $Config{perl_patchlevel}"
183 417 50       13412 if $Config{perl_patchlevel};
184 417         3933 return $pv;
185             }
186              
187             sub _generated_by {
188 21     21   494 return "# Generated by CPAN::Reporter "
189             . "$CPAN::Reporter::History::VERSION\n";
190             }
191              
192             #--------------------------------------------------------------------------#
193             # _get_history_file
194             #--------------------------------------------------------------------------#
195              
196             sub _get_history_file {
197 289     289   1842 return File::Spec->catdir(
198             CPAN::Reporter::Config::_get_config_dir(), "reports-sent.db"
199             );
200             }
201              
202             #--------------------------------------------------------------------------#
203             # _get_old_history_file -- prior to 0.99_08
204             #--------------------------------------------------------------------------#
205              
206             sub _get_old_history_file {
207 36     36   387 return File::Spec->catdir(
208             CPAN::Reporter::Config::_get_config_dir(), "history.db"
209             );
210             }
211              
212             #--------------------------------------------------------------------------#
213             # _is_duplicate
214             #--------------------------------------------------------------------------#
215              
216             sub _is_duplicate {
217 155     155   718 my ($result) = @_;
218 155         1590 my $log_line = _format_history( $result );
219 155 100       3541 my $history = _open_history_file('<') or return;
220 132         650 my $found = 0;
221 132         2538 flock $history, LOCK_SH;
222 132         4290 while ( defined (my $line = <$history>) ) {
223 372 100       2139 if ( $line eq $log_line ) {
224 97         465 $found++;
225 97         422 last;
226             }
227             }
228 132         2091 $history->close;
229 132         4414 return $found;
230             }
231              
232             #--------------------------------------------------------------------------#
233             # _match
234             #--------------------------------------------------------------------------#
235              
236             sub _match {
237 428     429   705 my ($fields, $search) = @_;
238 428         979 for my $k ( keys %$search ) {
239 974 100       1796 next if $search->{$k} eq q{}; # empty string matches anything
240 844 100       2904 return unless $fields->{$k} eq $search->{$k};
241             }
242 87         671 return 1; # all keys matched
243             }
244              
245             #--------------------------------------------------------------------------#
246             # _open_history_file
247             #--------------------------------------------------------------------------#
248              
249             sub _open_history_file {
250 253   50 254   1875 my $mode = shift || '<';
251 253         1514 my $history_filename = _get_history_file();
252 253         5459 my $file_exists = -f $history_filename;
253              
254             # shortcut if reading and doesn't exist
255 253 100 100     3568 return if ( $mode eq '<' && ! $file_exists );
256              
257             # open it in the desired mode
258 231 50       4254 my $history = IO::File->new( $history_filename, $mode )
259             or $CPAN::Frontend->mywarn("CPAN::Reporter: couldn't open history file "
260             . "'$history_filename': $!\n");
261              
262             # if writing and it didn't exist before, initialize with header
263 231 100 100     48397 if ( substr($mode,0,1) eq '>' && ! $file_exists ) {
264 20         73 print {$history} _generated_by();
  20         173  
265             }
266              
267 231         1603 return $history;
268             }
269              
270             #--------------------------------------------------------------------------#
271             # _perl_version
272             #--------------------------------------------------------------------------#
273              
274             sub _perl_version {
275 423   33 424   82007 my $ver = shift || "$]";
276 423         13687 $ver =~ qr/(\d)\.(\d{3})(\d{0,3})/;
277 423   50     9854 my ($maj,$min,$pat) = (0 + ($1||0), 0 + ($2||0), 0 + ($3||0));
      50        
      50        
278 423         1935 my $pv;
279 423 50       2303 if ( $min < 6 ) {
280 0         0 $pv = $ver;
281             }
282             else {
283 423         2920 $pv = "$maj\.$min\.$pat";
284             }
285 423         1819 return $pv;
286             }
287              
288             #--------------------------------------------------------------------------#
289             # _record_history
290             #--------------------------------------------------------------------------#
291              
292             sub _record_history {
293 61     62   783 my ($result) = @_;
294 61         245 my $log_line = _format_history( $result );
295 61 50       303 my $history = _open_history_file('>>') or return;
296              
297 61         1598 flock( $history, LOCK_EX );
298 61         681 seek( $history, 0, 2 ); # seek to end of file
299 61         803 $history->print( $log_line );
300 61         2031 flock( $history, LOCK_UN );
301              
302 61         551 $history->close;
303 61         1453 return;
304             }
305              
306             #--------------------------------------------------------------------------#
307             # _split_history
308             #
309             # splits lines created with _format_history. Returns hash ref with
310             # phase, grade, dist, perl, platform
311             #--------------------------------------------------------------------------#
312              
313             sub _split_history {
314 428     428   776 my ($line) = @_;
315 428         660 chomp $line;
316 428         875 my %fields;
317 428         3065 @fields{qw/phase grade dist perl archname osvers/} =
318             $line =~ m{
319             ^(\S+) \s+ # phase
320             (\S+) \s+ # grade
321             (\S+) \s+ # dist
322             \(perl- ([^)]+) \) \s+ # (perl-version-patchlevel)
323             (\S+) \s+ # archname
324             (.+)$ # osvers
325             }xms;
326              
327             # return nothing if parse fails
328 428 50       1243 return if scalar keys %fields == 0;# grep { ! defined($_) } values %fields;
329             # otherwise return hashref
330 428         1116 return \%fields;
331             }
332              
333             1;
334              
335             # ABSTRACT: Read or write a CPAN::Reporter history log
336              
337             __END__