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   1228804 use strict;
  36         103  
  36         5228  
2             package CPAN::Reporter::History;
3              
4             our $VERSION = '1.2020';
5              
6 36     36   445 use vars qw/@ISA @EXPORT_OK/;
  36         166  
  36         2554  
7              
8 36     36   228 use Config;
  36         81  
  36         1764  
9 36     36   206 use Carp;
  36         123  
  36         2996  
10 36     36   215 use Fcntl qw/:flock/;
  36         88  
  36         6461  
11 36     36   257 use File::HomeDir ();
  36         68  
  36         1127  
12 36     36   207 use File::Path (qw/mkpath/);
  36         70  
  36         2229  
13 36     36   239 use File::Spec ();
  36         63  
  36         752  
14 36     36   1396 use IO::File ();
  36         2707  
  36         754  
15 36     36   197 use CPAN (); # for printing warnings
  36         76  
  36         737  
16 36     36   1649 use CPAN::Reporter::Config ();
  36         77  
  36         6099  
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   134 eval {
28 36         2080 my $temp_file = File::Spec->catfile(
29             File::Spec->tmpdir(), $$ . time()
30             );
31 36         466 my $fh = IO::File->new( $temp_file, "w" );
32 36         15698 flock $fh, LOCK_EX;
33 36         530 $fh->close;
34 36         10155 unlink $temp_file;
35             };
36 36 50       87495 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 2226278 croak "arguments to have_tested() must be key value pairs"
103             if @_ % 2;
104              
105 39         275 my $args = { @_ };
106              
107             my @bad_params = grep {
108 39         259 $_ !~ m{^(?:dist|phase|grade|perl|archname|osvers)$} } keys %$args;
  60         374  
109 39 100       358 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       286 $args->{grade} = uc $args->{grade} if defined $args->{grade};
115              
116             # default to current platform
117 38 100       267 $args->{perl} = _format_perl_version() unless defined $args->{perl};
118 38 100       161 $args->{archname} = _format_archname() unless defined $args->{archname};
119 38 100       459 $args->{osvers} = $Config{osvers} unless defined $args->{osvers};
120              
121 38         144 my @found;
122 38 50       208 my $history = _open_history_file('<') or return;
123 38         478 flock $history, LOCK_SH;
124 38         1021 <$history>; # throw away format line
125 38         225 while ( defined (my $line = <$history>) ) {
126 429 50       1105 my $fields = _split_history( $line ) or next;
127 429 100       824 push @found, $fields if _match($fields, $args);
128             }
129 38         276 $history->close;
130 38         1293 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   587 my ($result) = @_;
145 216         788 my $phase = $result->{phase};
146 216         919 my $grade = uc $result->{grade};
147 216         672 my $dist_name = $result->{dist_name};
148 216         628 my $perlver = "perl-" . _format_perl_version();
149 216         2413 my $osvers = $Config{osvers};
150 216         970 my $archname = _format_archname();
151 216         1099 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   2870 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       3657 if($Config{taint_disabled}) {
170 1 0       2 $archname .= '-silent' if($Config{taint_disabled} eq 'silent');
171 1         17 $archname .= '-no-taint-support';
172             }
173 256         872 return $archname;
174             }
175              
176             #--------------------------------------------------------------------------#
177             # _format_perl_version
178             #--------------------------------------------------------------------------#
179              
180             sub _format_perl_version {
181 417     417   5737 my $pv = _perl_version();
182             $pv .= " patch $Config{perl_patchlevel}"
183 417 50       16188 if $Config{perl_patchlevel};
184 417         2566 return $pv;
185             }
186              
187             sub _generated_by {
188 21     21   372 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   1514 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   601 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   539 my ($result) = @_;
218 155         1073 my $log_line = _format_history( $result );
219 155 100       4044 my $history = _open_history_file('<') or return;
220 132         1085 my $found = 0;
221 132         1788 flock $history, LOCK_SH;
222 132         3520 while ( defined (my $line = <$history>) ) {
223 372 100       1785 if ( $line eq $log_line ) {
224 97         289 $found++;
225 97         325 last;
226             }
227             }
228 132         1485 $history->close;
229 132         3712 return $found;
230             }
231              
232             #--------------------------------------------------------------------------#
233             # _match
234             #--------------------------------------------------------------------------#
235              
236             sub _match {
237 428     429   799 my ($fields, $search) = @_;
238 428         1211 for my $k ( keys %$search ) {
239 1008 100       2105 next if $search->{$k} eq q{}; # empty string matches anything
240 883 100       3416 return unless $fields->{$k} eq $search->{$k};
241             }
242 87         577 return 1; # all keys matched
243             }
244              
245             #--------------------------------------------------------------------------#
246             # _open_history_file
247             #--------------------------------------------------------------------------#
248              
249             sub _open_history_file {
250 253   50 254   1429 my $mode = shift || '<';
251 253         947 my $history_filename = _get_history_file();
252 253         9068 my $file_exists = -f $history_filename;
253              
254             # shortcut if reading and doesn't exist
255 253 100 100     2833 return if ( $mode eq '<' && ! $file_exists );
256              
257             # open it in the desired mode
258 231 50       3876 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     46869 if ( substr($mode,0,1) eq '>' && ! $file_exists ) {
264 20         58 print {$history} _generated_by();
  20         101  
265             }
266              
267 231         1235 return $history;
268             }
269              
270             #--------------------------------------------------------------------------#
271             # _perl_version
272             #--------------------------------------------------------------------------#
273              
274             sub _perl_version {
275 423   33 424   85341 my $ver = shift || "$]";
276 423         22276 $ver =~ qr/(\d)\.(\d{3})(\d{0,3})/;
277 423   50     7903 my ($maj,$min,$pat) = (0 + ($1||0), 0 + ($2||0), 0 + ($3||0));
      50        
      50        
278 423         930 my $pv;
279 423 50       1384 if ( $min < 6 ) {
280 0         0 $pv = $ver;
281             }
282             else {
283 423         1663 $pv = "$maj\.$min\.$pat";
284             }
285 423         1548 return $pv;
286             }
287              
288             #--------------------------------------------------------------------------#
289             # _record_history
290             #--------------------------------------------------------------------------#
291              
292             sub _record_history {
293 61     62   877 my ($result) = @_;
294 61         243 my $log_line = _format_history( $result );
295 61 50       202 my $history = _open_history_file('>>') or return;
296              
297 61         1569 flock( $history, LOCK_EX );
298 61         413 seek( $history, 0, 2 ); # seek to end of file
299 61         629 $history->print( $log_line );
300 61         5590 flock( $history, LOCK_UN );
301              
302 61         409 $history->close;
303 61         1613 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   910 my ($line) = @_;
315 428         736 chomp $line;
316 428         713 my %fields;
317 428         3512 @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       1116 return if scalar keys %fields == 0;# grep { ! defined($_) } values %fields;
329             # otherwise return hashref
330 428         2075 return \%fields;
331             }
332              
333             1;
334              
335             # ABSTRACT: Read or write a CPAN::Reporter history log
336              
337             __END__