File Coverage

blib/lib/EPublisher/Source/Plugin/MetaCPAN.pm
Criterion Covered Total %
statement 83 104 79.8
branch 15 28 53.5
condition 1 3 33.3
subroutine 9 9 100.0
pod 1 1 100.0
total 109 145 75.1


line stmt bran cond sub pod time code
1             package EPublisher::Source::Plugin::MetaCPAN;
2              
3             # ABSTRACT: Get POD from distributions via MetaCPAN
4              
5 4     4   98592 use strict;
  4         12  
  4         377  
6 4     4   24 use warnings;
  4         8  
  4         117  
7              
8 4     4   4796 use Data::Dumper;
  4         44390  
  4         344  
9 4     4   4443 use Encode;
  4         44565  
  4         434  
10 4     4   115 use File::Basename;
  4         10  
  4         366  
11 4     4   3406 use MetaCPAN::API;
  4         670183  
  4         199  
12              
13 4     4   4371 use EPublisher::Source::Base;
  4         1426  
  4         139  
14 4     4   10194 use EPublisher::Utils::PPI qw(extract_pod_from_code);
  4         670223  
  4         5472  
15              
16             our @ISA = qw( EPublisher::Source::Base );
17              
18             our $VERSION = 0.23;
19              
20             # implementing the interface to EPublisher::Source::Base
21             sub load_source{
22 3     3 1 1433 my ($self) = @_;
23              
24 3         12 $self->publisher->debug( '100: start ' . __PACKAGE__ );
25              
26 3         48 my $options = $self->_config;
27            
28 3 50       35 return '' unless $options->{module};
29              
30 3         8 my $module = $options->{module}; # the name of the CPAN-module
31 3         5 my $dont_merge_release = $options->{onlythis};
32 3         73 my $mcpan = MetaCPAN::API->new;
33              
34             # metacpan does not handle ".pm" in dist names
35 3         328 my $release_name_metacpan = $module;
36 3         8 $release_name_metacpan =~ s/\.pm\z//;
37              
38             # fetching the requested module from metacpan
39 3         12 $self->publisher->debug( "103: fetch release $module ($release_name_metacpan)" );
40              
41             # if just the one and only POD from the modules name and not the entire
42             # release is wanted, we just fetch it and return
43 3 50       37 if ($dont_merge_release) {
44              
45 0         0 my $result;
46              
47             eval {
48 0         0 $result = $mcpan->pod(
49             module => $release_name_metacpan,
50             'content-type' => 'text/x-pod',
51             );
52 0         0 1;
53 0 0       0 } or do {
54 0         0 $self->publisher->debug(
55             "103: Can't retrieve pod for $release_name_metacpan"
56             );
57 0         0 return;
58             };
59              
60 0         0 my @pod = ();
61 0         0 my $info = { pod => $result, filename => '', title => $module };
62 0         0 push (@pod, $info);
63              
64             # EXIT!
65 0         0 return @pod;
66             }
67             # ELSE we go on and build the entire release...
68              
69             # if there is a wrong module-name we write a debug-message and return
70             # an empty array
71 3         5 my $module_result;
72             eval {
73 3         17 $module_result =
74             $mcpan->fetch( 'release/' . $release_name_metacpan );
75 2         52331 1;
76 3 100       6 } or do {
77 1         68712 $self->publisher->debug(
78             "103: release $release_name_metacpan does not exist"
79             );
80 1         162 return;
81             };
82              
83             # if we reached here the module-call was probably fine...
84             # so we print out what we have got
85 2         20 $self->publisher->debug(
86             "103: fetch result: " . Dumper $module_result
87             );
88              
89             # get the manifest with module-author and modulename-moduleversion
90 2         2255 $self->publisher->debug( '103: get MANIFEST' );
91 2         29 my $manifest;
92             eval {
93 2         21 $manifest = $mcpan->source(
94             author => $module_result->{author},
95             release => $module_result->{name},
96             path => 'MANIFEST',
97             );
98 2 50       4 } or do {
99 0         0 $self->publisher->debug(
100             "103: Cannot get MANIFEST",
101             );
102 0         0 return;
103             };
104              
105             # make a list from all possible POD-files in the lib directory
106 2         28189 my @files = split /\n/, $manifest;
107              
108             #$self->publisher->debug( "103: files from manifest: " . join ', ', @files );
109              
110             # some MANIFESTS (like POD::Parser) have comments after the filenames,
111             # so we match against an optional \s instead of \z
112             # the manifest, in POD::Parser in looks e.g. like this:
113             #
114             # lib/Pod/Usage.pm -- The Pod::Usage module source
115             # lib/Pod/Checker.pm -- The Pod::Checker module source
116             # lib/Pod/Find.pm -- The Pod::Find module source
117 93 100       396 my @pod_files = grep{
118 2         12 /^.*\.p(?:od|m|l)(?:\s|$)/ # all POD everywhere
119             and not
120             /^(?:example\/|x?t\/|inc\/)/ # but not in example/ or t/ or xt/ or inc/
121             }@files;
122              
123             # especially in App::* dists the most important documentation
124             # is often in the scripts
125 93 50       227 push @pod_files, grep {
126 2         8 /^bin\//
127             and not
128             /^.*\.p(?:od|m|l)(?:\s|$)/
129             }@files;
130              
131             # here whe store POD if we find some later on
132 2         5 my @pod;
133              
134             # look for POD
135 2         8 for my $file ( @pod_files ) {
136              
137             # we match the filename again, in case there are comments in
138             # the manifest, in POD::Parser in looks e.g. like this:
139             #
140             # lib/Pod/Usage.pm -- The Pod::Usage module source
141             # lib/Pod/Checker.pm -- The Pod::Checker module source
142             # lib/Pod/Find.pm -- The Pod::Find module source
143              
144 15         295 my ($path) = split /\s/, $file;
145 15 0 33     115 next if $path !~ m{ \. p(?:od|m|l) \z }x && $path !~ m{ \A bin/ }x;
146              
147 15         32 $file = $path;
148              
149             # the call below ($mcpan->pod()) fails if there is no POD in a
150             # module so this is why I filter all the modules. I check if they
151             # have any line BEGINNING with '=head1' ore similar
152 15         27 my $source;
153             eval {
154 15         186 $source = $mcpan->source(
155             author => $module_result->{author},
156             release => $module_result->{name},
157             path => $file,
158             );
159 15         210841 1;
160 15 50       38 } or do {
161 0         0 $self->publisher->debug(
162             "103: Cannot get source for $file",
163             );
164 0         0 return;
165             };
166              
167 15         150 $self->publisher->debug( "103: source of $file found" );
168              
169             # The Moose-Project made me write this filtering Regex, because
170             # they have .pm's without POD, and also with nonsense POD which
171             # still fails if you call $mcpan->pod
172 15         302 my $pod_src;
173 15 100       322 if ( $source =~ m{ ^=head[1234] }xim ) {
174              
175             eval {
176 13         159 $pod_src = $mcpan->pod(
177             author => $module_result->{author},
178             release => $module_result->{name},
179             path => $file,
180             'content-type' => 'text/x-pod',
181             );
182              
183 13         269825 1;
184 13 50       33 } or do{ $self->publisher->debug( $@ ); next; };
  0         0  
  0         0  
185              
186 13 50       55 if (!$pod_src) {
187 0         0 $self->publisher->debug( "103: empty pod handle" );
188 0         0 next;
189             }
190              
191 13 50       260 if ( $pod_src =~ m/ \A ({.*) /xs ) {
192 0         0 $self->publisher->debug( "103: error message: $1" );
193 0         0 next;
194             }
195             else {
196 13         137 $self->publisher->debug( "103: got pod" );
197             }
198              
199             # metacpan always provides utf-8 encoded data, so we have to decode it
200             # otherwise the target plugins may produce garbage
201 13         300 $pod_src = decode( 'utf-8', $pod_src );
202              
203             }
204             else {
205             # if there is no head we consider this POD unvalid
206 2         13 next;
207             }
208            
209             # check if $result is always only the Pod
210             #push @pod, extract_pod_from_code( $result );
211 13         2771 my $filename = basename $file;
212 13         34 my $title = $file;
213              
214 13         93 $title =~ s{^(?:lib|bin)/}{};
215 13         89 $title =~ s{\.p(?:m|od|l)\z}{};
216 13         68 $title =~ s{/}{::}g;
217            
218 13         139 my $info = { pod => $pod_src, filename => $filename, title => $title };
219 13         38 push @pod, $info;
220              
221             # make some nice debug output for what is in $info
222 13         28 my $pod_short;
223 13 50       78 if ($pod_src =~ m/(.{50})/s) {
224 13         53 $pod_short = $1 . '[...]';
225             }
226             else {
227 0         0 $pod_short = $pod_src;
228             }
229 13         69 $self->publisher->debug(
230             "103: passed info: "
231             . "filename => $filename, "
232             . "title => $title, "
233             . "pod => $pod_short"
234             );
235             }
236              
237             # voila
238 2         705 return @pod;
239             }
240              
241             1;
242              
243              
244              
245             =pod
246              
247             =head1 NAME
248              
249             EPublisher::Source::Plugin::MetaCPAN - Get POD from distributions via MetaCPAN
250              
251             =head1 VERSION
252              
253             version 0.23
254              
255             =head1 SYNOPSIS
256              
257             my $source_options = { type => 'MetaCPAN', module => 'Moose' };
258             my $url_source = EPublisher::Source->new( $source_options );
259             my @pod = $url_source->load_source;
260              
261             =encoding utf8
262              
263             =head1 OPTIONS
264              
265             Those options can be passed to this plugin:
266              
267             =over 4
268              
269             =item * module
270              
271             =item * onlythis
272              
273             =back
274              
275             =head1 METHODS
276              
277             =head2 load_source
278              
279             my @pod = $url_source->load_source;
280              
281             returns a list of documentation for the given distribution. Each element
282             of the list is a hashref that looks like
283              
284             {
285             pod => '=head1 EPublisher...',
286             filename => 'Epublisher.pm',
287             title => 'EPublisher,
288             }
289              
290             Where
291              
292             =over 4
293              
294             =item * pod
295              
296             Complete POD documentation extracted from the file
297              
298             =item * filename
299              
300             Basename of the file where the documentation was found
301              
302             =item * title
303              
304             Full path of the file with some substitutions:
305              
306             =over 4
307              
308             =item * removed leading "bin/" or "lib/"
309              
310             =item * removed file suffix (".pm", ".pl", ".pod")
311              
312             =item * replaced "/" with "::"
313              
314             =back
315              
316             =back
317              
318             =head1 AUTHOR
319              
320             Renee Baecker , Boris Daeppen
321              
322             =head1 COPYRIGHT AND LICENSE
323              
324             This software is Copyright (c) 2012 by Renee Bäcker, Boris Däppen.
325              
326             This is free software, licensed under:
327              
328             The Artistic License 2.0 (GPL Compatible)
329              
330             =cut
331              
332              
333             __END__