line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package CPAN::Access::AdHoc; |
2
|
|
|
|
|
|
|
|
3
|
4
|
|
|
4
|
|
96993
|
use 5.008; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
130
|
|
4
|
|
|
|
|
|
|
|
5
|
4
|
|
|
4
|
|
17
|
use strict; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
169
|
|
6
|
4
|
|
|
4
|
|
15
|
use warnings; |
|
4
|
|
|
|
|
5
|
|
|
4
|
|
|
|
|
92
|
|
7
|
|
|
|
|
|
|
|
8
|
4
|
|
|
4
|
|
1022
|
use Config::Tiny (); |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
64
|
|
9
|
4
|
|
|
4
|
|
1201
|
use CPAN::Access::AdHoc::Archive; |
|
4
|
|
|
|
|
12
|
|
|
4
|
|
|
|
|
152
|
|
10
|
4
|
|
|
|
|
520
|
use CPAN::Access::AdHoc::Util qw{ |
11
|
|
|
|
|
|
|
:carp __attr __cache __expand_distribution_path __guess_media_type |
12
|
4
|
|
|
4
|
|
20
|
}; |
|
4
|
|
|
|
|
5
|
|
13
|
4
|
|
|
4
|
|
2258
|
use Digest::SHA (); |
|
4
|
|
|
|
|
9818
|
|
|
4
|
|
|
|
|
101
|
|
14
|
4
|
|
|
4
|
|
1176
|
use File::HomeDir (); |
|
4
|
|
|
|
|
56
|
|
|
4
|
|
|
|
|
74
|
|
15
|
4
|
|
|
4
|
|
19
|
use File::Spec (); |
|
4
|
|
|
|
|
5
|
|
|
4
|
|
|
|
|
40
|
|
16
|
4
|
|
|
4
|
|
15
|
use IO::File (); |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
35
|
|
17
|
4
|
|
|
4
|
|
2219
|
use LWP::UserAgent (); |
|
4
|
|
|
|
|
36187
|
|
|
4
|
|
|
|
|
111
|
|
18
|
4
|
|
|
4
|
|
27
|
use LWP::Protocol (); |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
52
|
|
19
|
4
|
|
|
4
|
|
15
|
use Module::Pluggable::Object; |
|
4
|
|
|
|
|
4
|
|
|
4
|
|
|
|
|
66
|
|
20
|
4
|
|
|
4
|
|
2147
|
use Safe; |
|
4
|
|
|
|
|
103202
|
|
|
4
|
|
|
|
|
246
|
|
21
|
4
|
|
|
4
|
|
37
|
use Scalar::Util qw{ blessed }; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
248
|
|
22
|
4
|
|
|
4
|
|
1731
|
use Text::ParseWords (); |
|
4
|
|
|
|
|
4170
|
|
|
4
|
|
|
|
|
103
|
|
23
|
4
|
|
|
4
|
|
25
|
use URI (); |
|
4
|
|
|
|
|
4
|
|
|
4
|
|
|
|
|
4773
|
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
our $VERSION = '0.000_194'; |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
# In the following list of attribute names, 'config' must be first |
28
|
|
|
|
|
|
|
# because it supplies default values for everything else. 'cpan' must be |
29
|
|
|
|
|
|
|
# after 'default_cpan_source' because 'default_cpan_source' determines |
30
|
|
|
|
|
|
|
# how the default value of 'cpan' is computed. |
31
|
|
|
|
|
|
|
my @attributes = qw{ |
32
|
|
|
|
|
|
|
config __debug http_error_handler default_cpan_source cpan |
33
|
|
|
|
|
|
|
}; |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
sub new { |
36
|
12
|
|
|
12
|
1
|
3956
|
my ( $class, %arg ) = @_; |
37
|
|
|
|
|
|
|
|
38
|
12
|
|
33
|
|
|
77
|
my $self = bless {}, ref $class || $class; |
39
|
|
|
|
|
|
|
|
40
|
12
|
|
|
|
|
37
|
$self->__init( \%arg ); |
41
|
|
|
|
|
|
|
|
42
|
12
|
100
|
|
|
|
30
|
%arg |
43
|
|
|
|
|
|
|
and __wail( 'Unknown attribute(s): ', join ', ', sort keys %arg ); |
44
|
|
|
|
|
|
|
|
45
|
11
|
|
|
|
|
34
|
return $self; |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub __init { |
49
|
12
|
|
|
12
|
|
16
|
my ( $self, $arg ) = @_; |
50
|
|
|
|
|
|
|
|
51
|
12
|
|
|
|
|
23
|
foreach my $name ( @attributes ) { |
52
|
60
|
|
|
|
|
160
|
$self->$name( delete $arg->{$name} ); |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
12
|
|
|
|
|
16
|
return $self; |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub corpus { |
59
|
1
|
|
|
1
|
1
|
219
|
my ( $self, $cpan_id ) = @_; |
60
|
1
|
|
|
|
|
2
|
$cpan_id = uc $cpan_id; |
61
|
|
|
|
|
|
|
|
62
|
1
|
|
|
|
|
4
|
my $prefix = join '/', |
63
|
|
|
|
|
|
|
substr( $cpan_id, 0, 1 ), |
64
|
|
|
|
|
|
|
substr( $cpan_id, 0, 2 ), |
65
|
|
|
|
|
|
|
$cpan_id; |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
return ( |
68
|
0
|
|
|
|
|
0
|
map { "$prefix/$_" } |
|
0
|
|
|
|
|
0
|
|
69
|
1
|
|
|
|
|
2
|
grep { $_ !~ m/ [.] meta \z /smx } |
70
|
1
|
|
|
|
|
2
|
sort keys %{ $self->fetch_distribution_checksums( $cpan_id ) } |
71
|
|
|
|
|
|
|
); |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
sub exists : method { ## no critic (ProhibitBuiltinHomonyms) |
75
|
0
|
|
|
0
|
1
|
0
|
my ( $self, $path ) = @_; |
76
|
|
|
|
|
|
|
|
77
|
0
|
|
|
|
|
0
|
return $self->_request_path( head => $path )->is_success(); |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
sub fetch { |
81
|
24
|
|
|
24
|
1
|
2780
|
my ( $self, $path ) = @_; |
82
|
|
|
|
|
|
|
|
83
|
24
|
|
|
|
|
63
|
my $rslt = $self->_request_path( get => $path ); |
84
|
|
|
|
|
|
|
|
85
|
24
|
100
|
|
|
|
29988
|
$rslt->is_success |
86
|
|
|
|
|
|
|
or return $self->http_error_handler()->( $self, $path, $rslt ); |
87
|
|
|
|
|
|
|
|
88
|
17
|
|
|
|
|
172
|
__guess_media_type( $rslt, $path ); |
89
|
|
|
|
|
|
|
|
90
|
17
|
|
|
|
|
64
|
$self->_checksum( $rslt ); |
91
|
|
|
|
|
|
|
|
92
|
17
|
100
|
|
|
|
120
|
my $archive = |
93
|
|
|
|
|
|
|
CPAN::Access::AdHoc::Archive->__handle_http_response( $rslt ) |
94
|
|
|
|
|
|
|
or __wail( sprintf q{Unsupported Content-Type '%s'}, |
95
|
|
|
|
|
|
|
$rslt->header( 'Content-Type' ) ); |
96
|
|
|
|
|
|
|
|
97
|
16
|
|
|
|
|
251
|
return $archive; |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub fetch_author_index { |
101
|
2
|
|
|
2
|
1
|
1771
|
my ( $self ) = @_; |
102
|
|
|
|
|
|
|
|
103
|
2
|
|
|
|
|
9
|
my $cache = $self->__cache(); |
104
|
2
|
50
|
|
|
|
13
|
exists $cache->{author_index} |
105
|
|
|
|
|
|
|
and return $cache->{author_index}; |
106
|
|
|
|
|
|
|
|
107
|
2
|
|
|
|
|
7
|
my $author_details = $self->fetch( 'authors/01mailrc.txt.gz' ); |
108
|
2
|
50
|
|
|
|
10
|
_got_archive( $author_details ) |
109
|
|
|
|
|
|
|
or return $author_details; |
110
|
2
|
|
|
|
|
8
|
$author_details = $author_details->get_item_content(); |
111
|
|
|
|
|
|
|
|
112
|
2
|
100
|
|
|
|
21
|
my $fh = IO::File->new( \$author_details, '<' ) |
113
|
|
|
|
|
|
|
or __wail( "Unable to open string reference: $!" ); |
114
|
|
|
|
|
|
|
|
115
|
1
|
|
|
|
|
49
|
my %author_index; |
116
|
1
|
|
|
|
|
6
|
while ( <$fh> ) { |
117
|
2
|
|
|
|
|
9
|
s/ \s+ \z //smx; |
118
|
2
|
|
|
|
|
9
|
my ( undef, $cpan_id, $address ) = Text::ParseWords::parse_line( |
119
|
|
|
|
|
|
|
qr{ \s+ }smx, 0, $_ ); |
120
|
2
|
|
|
|
|
205
|
( my $name = $address ) =~ s{ \s+ < (.*) > }{}smx; |
121
|
2
|
|
|
|
|
4
|
my $mail_addr = $1; |
122
|
2
|
|
|
|
|
14
|
$author_index{ uc $cpan_id } = { |
123
|
|
|
|
|
|
|
name => $name, |
124
|
|
|
|
|
|
|
address => $mail_addr, |
125
|
|
|
|
|
|
|
}; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
1
|
|
|
|
|
5
|
return ( $cache->{author_index} = \%author_index ); |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub fetch_distribution_archive { |
132
|
4
|
|
|
4
|
1
|
1997
|
my ( $self, $distribution ) = @_; |
133
|
4
|
|
|
|
|
18
|
my $path = __expand_distribution_path( $distribution ); |
134
|
4
|
|
|
|
|
22
|
return $self->fetch( "authors/id/$path" ); |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub fetch_distribution_checksums { |
138
|
9
|
|
|
9
|
1
|
655
|
my ( $self, $distribution ) = @_; |
139
|
|
|
|
|
|
|
|
140
|
9
|
|
|
|
|
46
|
$distribution =~ s{ \A ( . ) / \1 ( . ) / \1 \2 ( [^/]* ) } |
141
|
|
|
|
|
|
|
{$1$2$3}smx; |
142
|
9
|
100
|
|
|
|
33
|
$distribution =~ m{ / }smx |
143
|
|
|
|
|
|
|
or $distribution .= '/'; |
144
|
9
|
50
|
|
|
|
49
|
$distribution =~ m{ \A ( .* / ) ( [^/]* ) \z }smx |
145
|
|
|
|
|
|
|
or __wail( "Invalid distribution '$distribution'" ); |
146
|
9
|
|
|
|
|
22
|
my ( $dir, $file ) = ( $1, $2 ); |
147
|
|
|
|
|
|
|
|
148
|
9
|
50
|
|
|
|
28
|
$file eq 'CHECKSUMS' |
149
|
|
|
|
|
|
|
and $file = ''; |
150
|
9
|
|
|
|
|
39
|
my $path = __expand_distribution_path( $dir . 'CHECKSUMS' ); |
151
|
9
|
|
|
|
|
43
|
( $dir = $path ) =~ s{ [^/]* \z }{}smx; |
152
|
|
|
|
|
|
|
|
153
|
9
|
|
|
|
|
31
|
my $cache = $self->__cache(); |
154
|
|
|
|
|
|
|
|
155
|
9
|
100
|
|
|
|
28
|
if ( ! $cache->{checksums}{$dir} ) { |
156
|
5
|
|
|
|
|
23
|
my $archive = $self->fetch( "authors/id/$path" ); |
157
|
4
|
50
|
|
|
|
14
|
_got_archive( $archive ) |
158
|
|
|
|
|
|
|
or return $archive; |
159
|
4
|
|
|
|
|
16
|
$cache->{checksums}{$dir} = _eval_string( |
160
|
|
|
|
|
|
|
$archive->get_item_content() ); |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
8
|
100
|
|
|
|
349
|
$file eq '' |
164
|
|
|
|
|
|
|
and return $cache->{checksums}{$dir}; |
165
|
6
|
|
|
|
|
36
|
return $cache->{checksums}{$dir}{$file}; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
# TODO finish implementing error handling. See above, _got_archive(). |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
sub fetch_module_index { |
171
|
5
|
|
|
5
|
1
|
1104
|
my ( $self ) = @_; |
172
|
|
|
|
|
|
|
|
173
|
5
|
|
|
|
|
23
|
my $cache = $self->__cache(); |
174
|
|
|
|
|
|
|
|
175
|
0
|
|
|
|
|
0
|
exists $cache->{module_index} |
176
|
|
|
|
|
|
|
and return wantarray ? |
177
|
5
|
50
|
|
|
|
22
|
@{ $cache->{module_index} } : |
|
|
100
|
|
|
|
|
|
178
|
|
|
|
|
|
|
$cache->{module_index}[0]; |
179
|
|
|
|
|
|
|
|
180
|
4
|
|
|
|
|
8
|
my ( $meta, %module ); |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# The only way this can return undef is if the http_error_handler |
183
|
|
|
|
|
|
|
# returns it. We take that as a request to cache an empty index. |
184
|
4
|
100
|
|
|
|
12
|
if ( my $packages_details = $self->fetch( |
185
|
|
|
|
|
|
|
'modules/02packages.details.txt.gz' ) ) { |
186
|
2
|
|
|
|
|
9
|
$packages_details = $packages_details->get_item_content(); |
187
|
|
|
|
|
|
|
|
188
|
2
|
100
|
|
|
|
14
|
my $fh = IO::File->new( \$packages_details, '<' ) |
189
|
|
|
|
|
|
|
or __wail( "Unable to open string reference: $!" ); |
190
|
|
|
|
|
|
|
|
191
|
1
|
|
|
|
|
1096
|
$meta = $self->_read_meta( $fh ); |
192
|
|
|
|
|
|
|
|
193
|
1
|
|
|
|
|
3
|
while ( <$fh> ) { |
194
|
3
|
|
|
|
|
4
|
chomp; |
195
|
3
|
|
|
|
|
11
|
my ( $mod, @info ) = split qr{ \s+ }smx; |
196
|
|
|
|
|
|
|
## 'undef' eq $ver |
197
|
|
|
|
|
|
|
## and $ver = undef; |
198
|
3
|
|
|
|
|
4
|
my ( $pkg, $ver ) = reverse @info; |
199
|
3
|
50
|
|
|
|
6
|
defined $ver or $ver = 'undef'; |
200
|
3
|
|
|
|
|
20
|
$module{$mod} = { |
201
|
|
|
|
|
|
|
distribution => $pkg, |
202
|
|
|
|
|
|
|
version => $ver, |
203
|
|
|
|
|
|
|
}; |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
} else { |
207
|
1
|
|
|
|
|
18
|
$meta = {}; |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
2
|
|
|
|
|
15
|
$cache->{module_index} = [ \%module, $meta ]; |
211
|
|
|
|
|
|
|
|
212
|
2
|
100
|
|
|
|
12
|
return wantarray ? ( \%module, $meta ) : \%module; |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
sub fetch_registered_module_index { |
216
|
2
|
|
|
2
|
1
|
1411
|
my ( $self ) = @_; |
217
|
|
|
|
|
|
|
|
218
|
2
|
|
|
|
|
26
|
my $cache = $self->__cache(); |
219
|
0
|
|
|
|
|
0
|
exists $cache->{registered_module_index} |
220
|
|
|
|
|
|
|
and return wantarray ? |
221
|
2
|
0
|
|
|
|
8
|
@{ $cache->{registered_module_index} } : |
|
|
50
|
|
|
|
|
|
222
|
|
|
|
|
|
|
$cache->{registered_module_index}[0]; |
223
|
|
|
|
|
|
|
|
224
|
2
|
|
|
|
|
9
|
my $packages_details = $self->fetch( |
225
|
|
|
|
|
|
|
'modules/03modlist.data.gz' |
226
|
|
|
|
|
|
|
)->get_item_content(); |
227
|
|
|
|
|
|
|
|
228
|
2
|
|
|
|
|
7
|
my ( $meta, $reg ); |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
{ |
231
|
|
|
|
|
|
|
|
232
|
2
|
100
|
|
|
|
3
|
my $fh = IO::File->new( \$packages_details, '<' ) |
|
2
|
|
|
|
|
17
|
|
233
|
|
|
|
|
|
|
or __wail( "Unable to open string reference: $!" ); |
234
|
|
|
|
|
|
|
|
235
|
1
|
|
|
|
|
55
|
$meta = $self->_read_meta( $fh ); |
236
|
|
|
|
|
|
|
|
237
|
1
|
|
|
|
|
4
|
local $/ = undef; |
238
|
1
|
|
|
|
|
7
|
$reg = <$fh>; |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
1
|
|
|
|
|
6
|
my $hash = _eval_string( "$reg\nCPAN::Modulelist->data();" ); |
242
|
|
|
|
|
|
|
|
243
|
1
|
|
|
|
|
76
|
$cache->{registered_module_index} = [ $hash, $meta ]; |
244
|
|
|
|
|
|
|
|
245
|
1
|
50
|
|
|
|
5
|
return wantarray ? ( $hash, $meta ) : $hash; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub flush { |
249
|
13
|
|
|
13
|
1
|
16
|
my ( $self ) = @_; |
250
|
13
|
|
|
|
|
20
|
delete $self->{'.cache'}; |
251
|
13
|
|
|
|
|
16
|
return $self; |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
sub indexed_distributions { |
255
|
1
|
|
|
1
|
1
|
3
|
my ( $self ) = @_; |
256
|
|
|
|
|
|
|
|
257
|
1
|
|
|
|
|
4
|
my $cache = $self->__cache(); |
258
|
|
|
|
|
|
|
|
259
|
0
|
|
|
|
|
0
|
$cache->{indexed_distributions} |
260
|
1
|
50
|
|
|
|
6
|
and return @{ $cache->{indexed_distributions} }; |
261
|
|
|
|
|
|
|
|
262
|
1
|
|
|
|
|
3
|
my $inx = $self->fetch_module_index(); |
263
|
|
|
|
|
|
|
|
264
|
1
|
|
|
|
|
2
|
my %pkg; |
265
|
1
|
|
|
|
|
2
|
foreach my $info ( values %{ $inx } ) { |
|
1
|
|
|
|
|
3
|
|
266
|
3
|
|
|
|
|
9
|
$pkg{$info->{distribution}}++; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
1
|
|
|
|
|
5
|
return @{ $cache->{indexed_distributions} = [ sort keys %pkg ] }; |
|
1
|
|
|
|
|
13
|
|
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# Set up the accessor/mutators. All mutators interpret undef as being a |
273
|
|
|
|
|
|
|
# request to restore the default, from the configuration if that exists, |
274
|
|
|
|
|
|
|
# or from the configured default code. |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
__PACKAGE__->__create_accessor_mutators( @attributes ); |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
sub _create_accessor_mutator_helper { |
279
|
40
|
|
|
40
|
|
38
|
my ( $class, $name, $code ) = @_; |
280
|
40
|
100
|
|
|
|
170
|
$class->can( $name ) |
281
|
|
|
|
|
|
|
and return; |
282
|
20
|
|
|
|
|
27
|
my $full_name = "${class}::$name"; |
283
|
4
|
|
|
4
|
|
26
|
no strict qw{ refs }; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
468
|
|
284
|
20
|
|
|
|
|
38
|
*$full_name = $code; |
285
|
20
|
|
|
|
|
21
|
return; |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
sub __create_accessor_mutators { |
289
|
4
|
|
|
4
|
|
10
|
my ( $class, @attrs ) = @_; |
290
|
4
|
|
|
|
|
10
|
foreach my $name ( @attrs ) { |
291
|
20
|
50
|
|
|
|
117
|
$class->can( $name ) and next; |
292
|
20
|
|
|
|
|
33
|
my $full_name = "${class}::$name"; |
293
|
|
|
|
|
|
|
$class->_create_accessor_mutator_helper( |
294
|
20
|
|
|
12
|
|
64
|
"__attr__${name}__validate" => sub { return $_[1] } ); |
|
12
|
|
|
|
|
13
|
|
295
|
|
|
|
|
|
|
$class->_create_accessor_mutator_helper( |
296
|
20
|
|
|
51
|
|
68
|
"__attr__${name}__post_assignment" => sub { return $_[1] } ); |
|
51
|
|
|
|
|
47
|
|
297
|
4
|
|
|
4
|
|
20
|
no strict qw{ refs }; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
5346
|
|
298
|
|
|
|
|
|
|
*$full_name = sub { |
299
|
173
|
|
|
173
|
|
4404
|
my ( $self, @arg ) = @_; |
300
|
173
|
|
|
|
|
533
|
my $attr = $self->__attr(); |
301
|
173
|
100
|
|
|
|
257
|
if ( @arg ) { |
302
|
66
|
|
|
|
|
59
|
my $value = $arg[0]; |
303
|
66
|
100
|
100
|
|
|
252
|
not defined $value |
304
|
|
|
|
|
|
|
and 'config' ne $name |
305
|
|
|
|
|
|
|
and $value = $self->config()->{_}{$name}; |
306
|
66
|
|
|
|
|
57
|
my $code; |
307
|
66
|
100
|
100
|
|
|
387
|
not defined $value |
308
|
|
|
|
|
|
|
and $code = $self->can( "__attr__${name}__default" ) |
309
|
|
|
|
|
|
|
and $value = $code->( $self ); |
310
|
66
|
50
|
|
|
|
341
|
$code = $self->can( "__attr__${name}__validate" ) |
311
|
|
|
|
|
|
|
and $value = $code->( $self, $value ); |
312
|
63
|
|
|
|
|
112
|
$attr->{$name} = $value; |
313
|
63
|
50
|
|
|
|
299
|
$code = $self->can( "__attr__${name}__post_assignment" ) |
314
|
|
|
|
|
|
|
and $code->( $self ); |
315
|
63
|
|
|
|
|
133
|
return $self; |
316
|
|
|
|
|
|
|
} else { |
317
|
107
|
|
|
|
|
1030
|
return $attr->{$name}; |
318
|
|
|
|
|
|
|
} |
319
|
20
|
|
|
|
|
80
|
}; |
320
|
|
|
|
|
|
|
} |
321
|
4
|
|
|
|
|
8
|
return; |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
{ |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
# Compute the config file's name and location. |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
( my $dist = __PACKAGE__ ) =~ s{ :: }{-}smxg; |
329
|
|
|
|
|
|
|
my $config_file = $dist . '.ini'; |
330
|
|
|
|
|
|
|
my $config_dir = File::HomeDir->my_dist_config( $dist ); |
331
|
|
|
|
|
|
|
my $config_path; |
332
|
|
|
|
|
|
|
defined $config_dir |
333
|
|
|
|
|
|
|
and $config_path = File::Spec->catfile( $config_dir, $config_file ); |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
sub __attr__config__default { |
336
|
12
|
|
|
12
|
|
16
|
my ( $self ) = @_; |
337
|
12
|
50
|
33
|
|
|
163
|
defined $config_path |
338
|
|
|
|
|
|
|
and -f $config_path |
339
|
|
|
|
|
|
|
and return Config::Tiny->read( $config_path ); |
340
|
12
|
|
|
|
|
63
|
return Config::Tiny->new(); |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
sub __attr__config__validate { |
345
|
13
|
|
|
13
|
|
17
|
my ( $self, $value ) = @_; |
346
|
|
|
|
|
|
|
|
347
|
13
|
|
|
|
|
19
|
my $err = "Attribute 'config' must be a file name or a " . |
348
|
|
|
|
|
|
|
"Config::Tiny reference"; |
349
|
13
|
50
|
|
|
|
32
|
if ( ref $value ) { |
350
|
13
|
100
|
|
|
|
16
|
eval { |
351
|
13
|
|
|
|
|
77
|
$value->isa( 'Config::Tiny' ); |
352
|
|
|
|
|
|
|
} or __wail( $err ); |
353
|
|
|
|
|
|
|
} else { |
354
|
0
|
0
|
|
|
|
0
|
-f $value |
355
|
|
|
|
|
|
|
or __wail( $err ); |
356
|
0
|
|
|
|
|
0
|
$value = Config::Tiny->read( $value ); |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
12
|
|
|
|
|
35
|
delete $value->{_}{config}; |
360
|
12
|
|
|
|
|
22
|
return $value; |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
# The rationale of the default order is: |
364
|
|
|
|
|
|
|
# 1) Mini cpan: guaranteed to be local, and since it is non-core, |
365
|
|
|
|
|
|
|
# the user had to install it, and can be presumed to be using it. |
366
|
|
|
|
|
|
|
# 2) CPAN minus: since it is non-core, the user had to install it, |
367
|
|
|
|
|
|
|
# and can be presumed to be using it. |
368
|
|
|
|
|
|
|
# 3) CPAN: It is core, but it needs to be set up to be used, and the |
369
|
|
|
|
|
|
|
# wrapper will detect if it has not been set up. |
370
|
|
|
|
|
|
|
# 4) CPANPLUS: It is core as of 5.10, and works out of the box, so |
371
|
|
|
|
|
|
|
# we can not presume that the user actually uses it. |
372
|
|
|
|
|
|
|
sub __attr__default_cpan_source__default { |
373
|
6
|
|
|
6
|
|
10
|
return 'CPAN::Mini,cpanm,CPAN,CPANPLUS'; |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
sub DEFAULT_HTTP_ERROR_HANDLER { |
377
|
4
|
|
|
4
|
0
|
8
|
my ( $self, $path, $resp ) = @_; |
378
|
4
|
|
|
|
|
9
|
my $url = $self->cpan() . $path; |
379
|
4
|
|
|
|
|
29
|
__wail( "Failed to get $url: ", $resp->status_line() ); |
380
|
|
|
|
|
|
|
} |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
sub __attr__http_error_handler__default { |
383
|
13
|
|
|
13
|
|
30
|
return \&DEFAULT_HTTP_ERROR_HANDLER; |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
sub __attr__http_error_handler__validate { |
387
|
15
|
|
|
15
|
|
20
|
my ( $self, $value ) = @_; |
388
|
15
|
50
|
|
|
|
41
|
'CODE' eq ref $value |
389
|
|
|
|
|
|
|
or __wail( |
390
|
|
|
|
|
|
|
q{Attribute 'http_error_handler' must be a code reference} |
391
|
|
|
|
|
|
|
); |
392
|
15
|
|
|
|
|
19
|
return $value; |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
sub __attr__cpan__post_assignment { |
396
|
12
|
|
|
12
|
|
17
|
my ( $self ) = @_; |
397
|
|
|
|
|
|
|
|
398
|
12
|
|
|
|
|
49
|
$self->flush(); |
399
|
|
|
|
|
|
|
|
400
|
12
|
|
|
|
|
8
|
return; |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
sub __attr__cpan__validate { |
404
|
13
|
|
|
13
|
|
63
|
my ( $self, $value ) = @_; |
405
|
|
|
|
|
|
|
|
406
|
13
|
|
|
|
|
101
|
$value = "$value"; # Stringify |
407
|
13
|
|
|
|
|
64
|
$value =~ s{ (?
|
408
|
|
|
|
|
|
|
|
409
|
13
|
50
|
|
|
|
43
|
my $url = URI->new( $value ) |
410
|
|
|
|
|
|
|
or _wail( "Bad URL '$value'" ); |
411
|
13
|
|
|
|
|
4284
|
$value = $url; |
412
|
|
|
|
|
|
|
|
413
|
13
|
|
|
|
|
61
|
my $scheme = $value->scheme(); |
414
|
13
|
100
|
66
|
|
|
338
|
$value->can( 'authority' ) |
415
|
|
|
|
|
|
|
and LWP::Protocol::implementor( $scheme ) |
416
|
|
|
|
|
|
|
or __wail ( "URL scheme $scheme: is unsupported" ); |
417
|
|
|
|
|
|
|
|
418
|
12
|
|
|
|
|
34586
|
return $value; |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
# Check the file's checksum if appropriate. |
422
|
|
|
|
|
|
|
# |
423
|
|
|
|
|
|
|
# The argument is the HTTP::Response object that contains the data to |
424
|
|
|
|
|
|
|
# check. This object is expected to have its Content-Location set to the |
425
|
|
|
|
|
|
|
# path relative to the root of the site. |
426
|
|
|
|
|
|
|
# |
427
|
|
|
|
|
|
|
# Files are not checked unless they are in authors/id/, and are not |
428
|
|
|
|
|
|
|
# named CHECKSUM. |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
sub _checksum { |
431
|
17
|
|
|
17
|
|
25
|
my ( $self, $rslt ) = @_; |
432
|
17
|
50
|
|
|
|
39
|
defined( my $path = $rslt->header( 'Content-Location' ) ) |
433
|
|
|
|
|
|
|
or return; |
434
|
17
|
100
|
|
|
|
466
|
$path =~ m{ \A authors/id/ ( [^/] ) / ( \1 [^/] ) / \2 }smx |
435
|
|
|
|
|
|
|
or return; |
436
|
8
|
100
|
|
|
|
33
|
$path =~ m{ /CHECKSUMS \z }smx |
437
|
|
|
|
|
|
|
and return; |
438
|
4
|
|
|
|
|
7
|
my $cks_path = $path; |
439
|
4
|
50
|
|
|
|
27
|
$cks_path =~ s{ \A authors/id/ }{}smx |
440
|
|
|
|
|
|
|
or return; |
441
|
4
|
50
|
|
|
|
16
|
my $cksum = $self->fetch_distribution_checksums( $cks_path ) |
442
|
|
|
|
|
|
|
or return; |
443
|
0
|
0
|
|
|
|
0
|
$cksum->{sha256} |
444
|
|
|
|
|
|
|
or return; |
445
|
0
|
|
|
|
|
0
|
my $got = Digest::SHA::sha256_hex( $rslt->content() ); |
446
|
0
|
0
|
|
|
|
0
|
$got eq $cksum->{sha256} |
447
|
|
|
|
|
|
|
or __wail( "Checksum failure on $path" ); |
448
|
0
|
|
|
|
|
0
|
return; |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
# Expand the default_cpan_source attribute into a list of class names, |
452
|
|
|
|
|
|
|
# each implementing one of the listed defaults. |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
{ |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
my $search_path = 'CPAN::Access::AdHoc::Default::CPAN'; |
457
|
|
|
|
|
|
|
my %defaulter = map { ( |
458
|
|
|
|
|
|
|
$_ => $_, |
459
|
|
|
|
|
|
|
substr( $_, length( $search_path ) + 2 ) => $_, |
460
|
|
|
|
|
|
|
) } Module::Pluggable::Object->new( |
461
|
|
|
|
|
|
|
search_path => $search_path, |
462
|
|
|
|
|
|
|
inner => 0, |
463
|
|
|
|
|
|
|
require => 1, |
464
|
|
|
|
|
|
|
)->plugins(); |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
sub __attr__default_cpan_source__validate { |
467
|
13
|
|
|
13
|
|
19
|
my ( $self, $value ) = @_; |
468
|
|
|
|
|
|
|
|
469
|
13
|
50
|
|
|
|
166
|
ref $value |
470
|
|
|
|
|
|
|
or $value = [ split qr{ \s* , \s* }smx, $value ]; |
471
|
|
|
|
|
|
|
|
472
|
13
|
50
|
|
|
|
57
|
'ARRAY' eq ref $value |
473
|
|
|
|
|
|
|
or __wail( q{Attribute 'default_cpan_source' takes an array } . |
474
|
|
|
|
|
|
|
q{reference or a comma-delimited string} ); |
475
|
13
|
|
|
|
|
15
|
my @rslt; |
476
|
13
|
|
|
|
|
16
|
foreach my $source ( @{ $value } ) { |
|
13
|
|
|
|
|
28
|
|
477
|
31
|
100
|
|
|
|
70
|
defined( my $class = $defaulter{$source} ) |
478
|
|
|
|
|
|
|
or __wail( "Unknown default_cpan_source '$source'" ); |
479
|
30
|
|
|
|
|
48
|
push @rslt, $class; |
480
|
|
|
|
|
|
|
} |
481
|
12
|
|
|
|
|
29
|
return \@rslt; |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
# Eval a string in a sandbox, and return the result. This was cribbed |
487
|
|
|
|
|
|
|
# _very_ heavily from CPAN::Distribution CHECKSUM_check_file(). |
488
|
|
|
|
|
|
|
sub _eval_string { |
489
|
5
|
|
|
5
|
|
7
|
my ( $string ) = @_; |
490
|
5
|
|
|
|
|
106
|
$string =~ s/ \015? \012 /\n/smxg; |
491
|
5
|
|
|
|
|
35
|
my $sandbox = Safe->new(); |
492
|
5
|
|
|
|
|
3757
|
$sandbox->permit_only( ':default' ); |
493
|
5
|
|
|
|
|
39
|
my $rslt = $sandbox->reval( $string ); |
494
|
5
|
50
|
|
|
|
2207
|
$@ and __wail( $@ ); |
495
|
5
|
|
|
|
|
25
|
return $rslt; |
496
|
|
|
|
|
|
|
} |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
# Return the argument if it is a CPAN::Access::AdHoc::Archive; otherwise |
499
|
|
|
|
|
|
|
# just return. |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
sub _got_archive { |
502
|
6
|
|
|
6
|
|
11
|
my ( $rtn ) = @_; |
503
|
6
|
50
|
33
|
|
|
87
|
blessed( $rtn ) |
504
|
|
|
|
|
|
|
and $rtn->isa( 'CPAN::Access::AdHoc::Archive' ) |
505
|
|
|
|
|
|
|
and return $rtn; |
506
|
0
|
|
|
|
|
0
|
return; |
507
|
|
|
|
|
|
|
} |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
# Get the repository URL from the first source that actually supplies |
510
|
|
|
|
|
|
|
# it. The CPAN::Access::AdHoc::Default::CPAN plug-ins are called in the |
511
|
|
|
|
|
|
|
# order specified in the default_cpan_source attribute, and the first |
512
|
|
|
|
|
|
|
# source that actually supplies a URL is used. If that source provides a |
513
|
|
|
|
|
|
|
# file: URL, the first such is returned. Otherwise the first URL is |
514
|
|
|
|
|
|
|
# returned, whatever its scheme. If no URL can be determined, we die. |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
sub __attr__cpan__default { |
517
|
9
|
|
|
9
|
|
11
|
my ( $self ) = @_; |
518
|
|
|
|
|
|
|
|
519
|
9
|
|
|
|
|
10
|
my $url; |
520
|
|
|
|
|
|
|
|
521
|
9
|
|
|
|
|
16
|
my $debug = $self->__debug(); |
522
|
|
|
|
|
|
|
|
523
|
9
|
|
|
|
|
13
|
foreach my $class ( @{ $self->default_cpan_source() } ) { |
|
9
|
|
|
|
|
13
|
|
524
|
|
|
|
|
|
|
|
525
|
9
|
50
|
|
|
|
71
|
my @url_list = $class->get_default() |
526
|
|
|
|
|
|
|
or next; |
527
|
|
|
|
|
|
|
|
528
|
9
|
|
|
|
|
21
|
foreach ( @url_list ) { |
529
|
12
|
100
|
|
|
|
35
|
m/ \A file: /smx |
530
|
|
|
|
|
|
|
or next; |
531
|
7
|
|
|
|
|
30
|
$url = $_; |
532
|
7
|
|
|
|
|
11
|
last; |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
|
535
|
9
|
100
|
|
|
|
17
|
defined $url |
536
|
|
|
|
|
|
|
or $url = $url_list[0]; |
537
|
|
|
|
|
|
|
|
538
|
9
|
50
|
|
|
|
15
|
$debug |
539
|
|
|
|
|
|
|
and warn "Debug - Default cpan '$url' from $class\n"; |
540
|
|
|
|
|
|
|
|
541
|
9
|
|
|
|
|
20
|
return $url; |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
|
544
|
0
|
|
|
|
|
0
|
__wail( 'No CPAN URL obtained from ' . $self->default_cpan_source() ); |
545
|
|
|
|
|
|
|
} |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
# modules/02packages.details.txt.gz and modules/03modlist.data.gz have |
548
|
|
|
|
|
|
|
# metadata at the top. This metadata is organized as lines of |
549
|
|
|
|
|
|
|
# key: value |
550
|
|
|
|
|
|
|
# with the key left-justified. Lines can be wrapped, with leading |
551
|
|
|
|
|
|
|
# spaces. |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
sub _read_meta { |
554
|
2
|
|
|
2
|
|
5
|
my ( $self, $fh ) = @_; |
555
|
2
|
|
|
|
|
3
|
my %meta; |
556
|
|
|
|
|
|
|
{ |
557
|
2
|
|
|
|
|
3
|
my ( $name, $value ); |
|
2
|
|
|
|
|
3
|
|
558
|
2
|
|
|
|
|
13
|
while ( <$fh> ) { |
559
|
14
|
|
|
|
|
16
|
chomp; |
560
|
14
|
100
|
|
|
|
35
|
m/ \S /smx or last; |
561
|
12
|
100
|
|
|
|
25
|
if ( s/ \A \s+ //smx ) { |
562
|
4
|
|
|
|
|
12
|
$meta{$name} .= " $_"; |
563
|
|
|
|
|
|
|
} else { |
564
|
8
|
|
|
|
|
40
|
( $name, $value ) = split qr{ : \s* }smx, $_, 2; |
565
|
8
|
|
|
|
|
30
|
$meta{$name} = $value; |
566
|
|
|
|
|
|
|
} |
567
|
|
|
|
|
|
|
} |
568
|
|
|
|
|
|
|
} |
569
|
2
|
|
|
|
|
5
|
return \%meta; |
570
|
|
|
|
|
|
|
} |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
# Request a path relative to the root of the CPAN repository. The |
573
|
|
|
|
|
|
|
# arguments are the request name (which must be a valid method for |
574
|
|
|
|
|
|
|
# LWP::UserAgent, something like 'get' or 'head'. The HTTP::Response |
575
|
|
|
|
|
|
|
# object is returned. |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
sub _request_path { |
578
|
24
|
|
|
24
|
|
37
|
my ( $self, $rqst, $path ) = @_; |
579
|
|
|
|
|
|
|
|
580
|
24
|
|
|
|
|
37
|
$path =~ s{ \A / }{}smx; |
581
|
|
|
|
|
|
|
|
582
|
24
|
|
33
|
|
|
183
|
my $ua = $self->{__user_agent} || LWP::UserAgent->new(); |
583
|
|
|
|
|
|
|
|
584
|
24
|
|
|
|
|
8073
|
my $url = $self->cpan() . $path; |
585
|
|
|
|
|
|
|
|
586
|
24
|
|
|
|
|
164
|
return $ua->$rqst( $url ); |
587
|
|
|
|
|
|
|
} |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
1; |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
__END__ |