File Coverage

blib/lib/Pod/Weaver/Section/AutoDoc.pm
Criterion Covered Total %
statement 274 285 96.1
branch 78 124 62.9
condition 7 15 46.6
subroutine 47 47 100.0
pod n/a
total 406 471 86.2


line stmt bran cond sub pod time code
1 1     1   1691404 use strictures 2;
  1         8  
  1         60  
2              
3             package Pod::Weaver::Section::AutoDoc;
4              
5             # ABSTRACT: Assemble documentation gathered from Sub::Documentation
6              
7 1     1   283 use Carp;
  1         2  
  1         73  
8 1     1   400 use Method::Signatures::WithDocumentation;
  1         2  
  1         6  
9 1     1   861 use Module::Metadata;
  1         4852  
  1         29  
10 1     1   5 use Sub::Documentation qw(search_documentation);
  1         1  
  1         37  
11 1     1   4 use List::MoreUtils qw(uniq);
  1         1  
  1         11  
12 1     1   307 use Class::Load qw(try_load_class);
  1         1  
  1         34  
13              
14 1     1   5 use Moose;
  1         0  
  1         7  
15 1     1   4572 use Moose::Util::TypeConstraints ();
  1         2  
  1         20  
16              
17             with 'Pod::Weaver::Role::Section';
18              
19 1     1   443 use Pod::Elemental::Element::Nested;
  1         35262  
  1         35  
20 1     1   7 use Pod::Elemental::Element::Pod5::Command;
  1         1  
  1         18  
21 1     1   3 use Pod::Elemental::Element::Pod5::Ordinary;
  1         1  
  1         18  
22 1     1   3 use Pod::Elemental::Element::Pod5::Verbatim;
  1         1  
  1         31  
23            
24             our $VERSION = '0.001'; # VERSION
25              
26 1 50   1   271 func _nested ($type, $command, $content, @children) {
  10 50   10   46  
  10 50       11  
  10         15  
  10         7  
  10         20  
  10         11  
  10         27  
27 10         259 return Pod::Elemental::Element::Nested->new({
28             type => $type,
29             command => $command,
30             content => $content,
31             children => \@children,
32             });
33             }
34              
35 1 50   1   139 func _command($command, $content = '') {
  30 100   30   54  
  30 50       21  
  30         42  
  30         41  
36 30         739 return Pod::Elemental::Element::Pod5::Command->new(
37             command => $command, content => $content
38             );
39             }
40              
41 1     1   135 func _list(@items) {
  7     7   18  
42 7         8 my @elements;
43 7         15 push @elements => _command(over => 4);
44 7         782 foreach my $item (@items) {
45 16         17 my ($text, @rest);
46 16 100       25 if (ref $item eq 'ARRAY') {
47 9         13 ($text, @rest) = @$item;
48             } else {
49 7         9 $text = $item;
50             }
51 16         30 push @elements => _command(item => '* '.$text);
52 16         1540 push @elements => @rest;
53             }
54 7         11 push @elements => _command('back');
55 7         664 return @elements;
56             }
57              
58 1     1   133 func _ordinary(@contents) {
  47     47   83  
59 47         44 map { Pod::Elemental::Element::Pod5::Ordinary->new(content => $_) } @contents
  47         1214  
60             }
61              
62 1     1   132 func _verbatim(@contents) {
  3     3   7  
63 3         6 map { Pod::Elemental::Element::Pod5::Verbatim->new(content => $_) } @contents
  3         88  
64             }
65              
66 1 50   1   140 func _subdoc_getoftype ($type, @doc) {
  52     52   63  
  52         36  
  52         94  
67 52         50 my @RV = map { $_->{documentation} } grep { $_->{type} eq $type } @doc;
  64         67  
  780         651  
68 52 100       109 return wantarray ? @RV : $RV[0];
69             }
70              
71 1     1   136 func _subdoc_getnames (@doc) {
  5     5   43  
72 5         8 uniq sort map { $_->{name} } @doc;
  120         153  
73             }
74              
75 1     1   138 func _filter_pkglist (@list) {
  5     5   24  
76 5         13 grep !m{^(?:UNIVERSAL|main)$}, @list
77             }
78              
79 1 50   1   132 func _get_parents ($ns) {
  3 50   3   9  
  3         6  
  3         7  
80 1     1   61 no strict 'refs'; ## no critic
  1         1  
  1         34  
81 3         3 _filter_pkglist(@{ $ns . '::ISA' });
  3         17  
82             }
83              
84 1 50 33 1   136 func _trim (Str $str!) {
  34 50   34   61  
  34 50       23  
  34         76  
  34         1764  
85 34         71 $str =~ s{^\s*}{}s;
86 34         94 $str =~ s{\s*$}{}s;
87 34         87 $str;
88             }
89              
90 1 50 66 1   133 func _tidy(Str $str!) {
  34 50   34   63  
  34 50       25  
  34         101  
  34         5212  
91 34 100       95 $str =~ m{^(?:\s*\n([ \t\r]+)|(\s+))\S}s or return $str;
92 25         31 my $indent = quotemeta $1;
93 25         106 $str =~ s{^$indent}{}mg;
94 25         31 $str =~ s{([\n]){2,}}{$1 x 2}seg;
  0         0  
95 25         33 _trim($str);
96             }
97              
98 1 50 33 1   142 func _indent(Str $str!, Str|Int $indent = 8) {
  3 50 66 3   7  
  3 50       3  
  3 50       8  
  3 50       183  
  3         17  
  3         1196  
99 3 50       14 if ($indent =~ m{^\d+$}s) {
100 3         9 $indent = ' ' x $indent;
101             }
102 3         24 $str =~ s{^(.+)$}{$indent$1}mgr;
103             }
104              
105 1 50   1   168 func _create_links_in_contraint ($constraint) {
  9 50   9   20  
  9         10  
  9         13  
106 9         22 $constraint = Moose::Util::TypeConstraints::normalize_type_constraint_name($constraint);
107 9 50       43 if ($constraint =~ m{\|}) {
108 0         0 my @constraints = split /\|/, $constraint;
109             #$constraint = Moose::Util::TypeConstraints::create_type_constraint_union($constraint)->type_constraints;
110 0         0 return join(' | ', map { _create_links_in_contraint($_) } @constraints);
  0         0  
111             }
112 9         17 $constraint = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($constraint);
113 9 50       12664 if (ref($constraint) =~ m{^Moose::Meta::TypeConstraint::(?:Class|Role)$}) {
    0          
114 9         225 my $class = $constraint->class;
115 9 50       70 return $class ? "L<$class>" : $class;
116             } elsif (ref($constraint) eq 'Moose::Meta::TypeConstraint::Parameterized') {
117 0         0 return sprintf '%s[ %s ]', $constraint->parameterized_from, _create_links_in_contraint($constraint->type_parameter);
118             } else {
119 0         0 return ''.$constraint;
120             }
121             }
122              
123 1 50   1   170 func _get_methods ($prefix, $parent, $type, @documentation) {
  5 50   5   12  
  5 50       5  
  5         10  
  5         5  
  5         10  
  5         2  
  5         19  
124 5         7 my @methods;
125 5         9 foreach my $name (_subdoc_getnames(@documentation)) {
126 8 50       2057 next if $name =~ m{^_};
127 8         8 my @subdoc = grep { $_->{name} eq $name } @documentation;
  234         213  
128 8 100       18 next unless _subdoc_getoftype(type => @subdoc) eq $type;
129 4         8 my $purpose = _subdoc_getoftype(purpose => @subdoc);
130 4         7 my @pods = _subdoc_getoftype(pod => @subdoc);
131 4         7 my @params = _subdoc_getoftype(param_signature => @subdoc);
132 4         5 my %params_desc = map {( $_ => 1 )} _subdoc_getoftype(param => @subdoc);
  9         15  
133 4         7 my $since = _subdoc_getoftype(since => @subdoc);
134 4         8 my @authors = _subdoc_getoftype(author => @subdoc);
135 4         8 my $returns = _subdoc_getoftype(returns => @subdoc);
136 4         7 my @throws = _subdoc_getoftype(throws => @subdoc);
137 4         8 my $example = _subdoc_getoftype(example => @subdoc);
138 4         7 my $deprecated = _subdoc_getoftype(deprecated => @subdoc);
139 4         8 my $signature = _subdoc_getoftype(signature => @subdoc);
140              
141 4         4 my @children;
142              
143 4 100       9 if (defined $parent) {
144 1         3 push @children => _ordinary("Inherited from L<$parent>");
145             }
146              
147 4 50       105 if (defined $purpose) {
148 4         13 push @children => _ordinary(_tidy($purpose));
149             }
150              
151 4 100       435 if (defined $example) {
152 3         7 push @children => _ordinary("B<Synopsis:>");
153 3         272 push @children => _verbatim(_indent(_tidy($example)));
154             }
155              
156 4 100       319 if (@params) {
157 3         7 push @children => _ordinary("B<Parameters:>");
158 3         272 my @list;
159 3         6 foreach my $param (@params) {
160 9         605 my ($param_type, $param_name, @param_opts) = @$param;
161 9         14 my @desc = map { s{^\s*\Q$param_name\E:\s*(.*)\s*$}{$1}r } map { delete $params_desc{$_}; $_ } grep { m{^\s*\Q$param_name\E:} } keys %params_desc;
  9         139  
  9         12  
  9         14  
  18         187  
162 9         16 push @list => [
163             (_create_links_in_contraint($param_type)." C<<< $param_name >>>".(@param_opts ? " (".join(', ', @param_opts).")" : "")),
164 9 50       18 map { _ordinary(_tidy($_)) } @desc
165             ];
166             }
167 3         353 push @children => _list(@list);
168             }
169              
170 4 50       12 if (keys %params_desc) {
171 0         0 push @children => _list(keys %params_desc);
172             }
173              
174 4         4 push @children => map { _ordinary(_tidy($_)) } @pods;
  6         293  
175              
176 4 100       286 if (defined $returns) {
177 3         7 push @children => _ordinary("B<Returns:>");
178 3         271 push @children => _ordinary(_tidy($returns));
179             }
180              
181 4 100       282 if (@throws) {
182 3         7 push @children => _ordinary("B<Throws:>");
183 3         270 my @list;
184 3         7 foreach my $throw (@throws) {
185 6         7 push @list => _tidy($throw);
186             }
187 3         6 push @children => _list(@list);
188             }
189              
190 4 100       9 if (defined $since) {
191 3         6 push @children => _ordinary("B<Available since:> "._trim($since));
192             }
193              
194 4 100       318 if (defined $deprecated) {
195 3         6 push @children => _ordinary("B<DEPRECATION WARNING:>");
196 3         274 push @children => _ordinary(_tidy($deprecated));
197             }
198              
199 4 100       284 if (@authors) {
200 3         6 push @children => _ordinary("B<Author:> ".join(', ', map { _trim($_) } @authors));
  6         7  
201             }
202              
203 4         283 my $fullname = $prefix.$name;
204 4 50       12 $fullname .= " ($signature)" if defined $signature;
205 4 100       8 $fullname .= " B<DEPRECATED>" if defined $deprecated;
206              
207 4         11 push @methods => _nested('command', 'head2', $fullname, @children);
208             }
209 5         5224 return @methods;
210             }
211              
212 1 50   1   168 func _proc_ns ($prefix, $super, $ns, @documentation) {
  3 50   3   8  
  3 50       6  
  3         7  
  3         3  
  3         6  
  3         4  
  3         13  
213 3         8 my @methods = _get_methods($prefix, $super, 'method', @documentation);
214              
215 3         10 my @parents = _get_parents( $ns );
216              
217 3         8 my %parents = ($ns => \@parents);
218              
219 3         8 foreach my $parent (@parents) {
220 1         4 @documentation = search_documentation(
221             package => $parent,
222             glob_type => 'CODE',
223             );
224 1         879 my $R = _proc_ns ($prefix, $parent, $parent, @documentation);
225 1         3 %parents = (%parents, %{ $R->{parents} });
  1         4  
226 1         2 push @methods => @{ $R->{methods} };
  1         4  
227             }
228              
229             return {
230 3         18 methods => \@methods,
231             parents => \%parents,
232             };
233             }
234              
235 1     1   202 use namespace::clean;
  1         1  
  1         7  
236              
237 1 50   1   141 method weave_section ($doc, $input) {
  2 50   2   178094  
  2 50       9  
  2         4  
  2         6  
  2         3  
  2         5  
238            
239 2         3 my $filename = $input->{filename};
240              
241 2         8 my $info = Module::Metadata->new_from_file( $filename );
242            
243 2         1383 my $module = $info->name;
244            
245 2 50 33     13 try_load_class($module) or require($filename) or croak("cannot load $module (in file $filename)");
246            
247 2         460 my @namespaces = _filter_pkglist($info->packages_inside);
248            
249 2         3 my (@methods, @functions, %parents);
250            
251 2         5 foreach my $ns (@namespaces) {
252 2         7 my @documentation = search_documentation(
253             package => $ns,
254             glob_type => 'CODE',
255             );
256              
257 2 50       1779 my $prefix = $module ne $ns ? "${ns}::" : "";
258            
259 2         6 my $R = _proc_ns($prefix, undef, $ns, @documentation);
260              
261 2         2 push @methods => @{ $R->{methods} };
  2         5  
262              
263 2         5 push @functions => _get_methods($prefix, undef, 'func', @documentation);
264              
265 2         5 %parents = (%parents, %{ $R->{parents} });
  2         11  
266              
267             }
268            
269 2 50       6 push @{ $doc->children } => _nested('command', 'head1', 'METHODS', @methods) if @methods;
  2         55  
270 2 100       728 push @{ $doc->children } => _nested('command', 'head1', 'FUNCTIONS', @functions) if @functions;
  1         22  
271            
272 2 50       201 if (keys %parents) {
273 2         3 my @extends;
274 2 100       5 if (keys %parents > 1) {
275 1         4 foreach my $ns (sort keys %parents) {
276 2         350 my @parents = @{ $parents{$ns} };
  2         4  
277 2 100       7 next unless @parents;
278 1         1 my @list;
279 1         2 foreach my $parent (@parents) {
280 1         4 push @list => "L<$parent>";
281             }
282 1         4 push @extends => _nested('command', 'head2', $ns, _list(@list));
283             }
284             } else {
285 1         3 my ($ns) = keys %parents;
286 1         1 my @parents = @{ $parents{$ns} };
  1         3  
287 1 50       3 if (@parents) {
288 0         0 my @list;
289 0         0 foreach my $parent (@parents) {
290 0         0 push @list => "L<$parent>";
291             }
292 0         0 push @extends => _list(@list);
293             }
294             }
295 2         3 push @{ $doc->children } => _nested('command', 'head1', 'EXTENDS', @extends);
  2         44  
296             }
297             }
298              
299             1;
300              
301             __END__
302              
303             =pod
304              
305             =head1 NAME
306              
307             Pod::Weaver::Section::AutoDoc - Assemble documentation gathered from Sub::Documentation
308              
309             =head1 VERSION
310              
311             version 0.001
312              
313             =head1 SYNOPSIS
314              
315             Put
316              
317             [AutoDoc]
318              
319             into your C<weaver.ini>.
320              
321             =head1 DESCRIPTION
322              
323             This module adds up to three new sections into your pod: L<METHODS|/"METHODS SECTION">, L<FUNCTIONS|/"FUNCTIONS SECTION"> and L<EXTENDS/"EXTENDS SECTION">.
324              
325             Any documentation gathered by L<Sub::Documentation> will be assembled to an auto-generated pod for each method/function. Attribute definitions are not supported yet.
326              
327             If the module extends some other module, and that module (or any module in the inheritance chain) uses L<Sub::Documentation>, than the inherited methods will be included in the documentation.
328              
329             =head1 SECTIONS
330              
331             The sections will be added in this order to your pod at the desired position.
332              
333             =head2 METHODS SECTION
334              
335             To distinguish I<code> objects as a class method, the param I<type> should contain I<method>. Only objects with that attribute are assembled in this section.
336              
337             First, a pod command (head2) with the method name and a brief attribute list is printed. This maybe append with C<<< B<DEPRECATED> >>>.
338              
339             Then these paragraphes are followed in this order:
340              
341             =over 4
342              
343             =item * A notice if the method was inherited from another module, with a link to it.
344              
345             =item * The I<purpose> of the method.
346              
347             =item * The synopsis of the method, identified by I<example>.
348              
349             =item * A list of all parameters with documentation (if available by identifier I<param>)
350              
351             =item * All additional documentation identified by I<pod>
352              
353             =item * The return value of the method, identified by I<returns>
354              
355             =item * All throwables, identified by I<throws>
356              
357             =item * A list, since when this method is available, identified by I<since>
358              
359             =item * The deprecation warning, identified by I<deprecated>
360              
361             =item * The list of all especially named authors, indentified by I<author>
362              
363             =back
364              
365             =head2 FUNCTIONS SECTION
366              
367             To distinguish I<code> objects as a class function, the param I<type> should contain I<func>. Only objects with that attribute are assembled in this section.
368              
369             The rules to not differ from L<methods|/"METHODS SECTION">, so see there for a detailed description.
370              
371             =head2 EXTENDS SECTION
372              
373             Inserts a simple list of all parent classes with links to them.
374              
375             =head1 FORMATTING TIPS
376              
377             Multiline attributes (I<Purpose>, I<Example>, I<Pod>, I<Returns>, I<Throws>, I<Deprecated> and I<Param>) are trimmed and re-indented by resetting all indentations to the first indentation with non-whitechars on the line. So, the following statement:
378              
379             sub xxx :
380             Pod(
381             Lorem
382             Ipsum
383             )
384             { ... }
385              
386             results in:
387              
388             =pod
389            
390             Lorem
391             Ipsum
392            
393             =cut
394              
395             But for the I<Example> attribute, the verbatim block is automatically indented. Thus,
396              
397             sub xxx :
398             Example(
399             my $xxx = xxx;
400             )
401             { ... }
402              
403             results in:
404              
405             =pod
406            
407             my $xxx = xxx;
408            
409             =cut
410              
411             The single-line attributes I<Since> and I<Author> should contain no line breaks.
412              
413             For some readers it might be confusing the read a subroutine definition with many attributes. Theres is no best practise at the moment, but I suggest this template:
414              
415             func foobar (Int $amount = 1) :
416             Purpose(
417             Prints out I<foo> and I<bar>
418             )
419             Example(
420             foobar(2); # prints two foos and two bars
421             )
422             Param(
423             $amount: how many foo and bar should be printed
424             )
425             Pod(
426             This function is an example to show you a fancy way for its documentation
427             )
428             Returns(
429             True on success
430             )
431             Throws(
432             An error message if there is no output device
433             )
434             Since(
435             1.000
436             )
437             Deprecated(
438             Use L</foobar_v2> instead.
439             )
440             Author(
441             John Doe
442             )
443             { ... }
444              
445             The resulting pod looks like:
446              
447             =head2 foobar ([ Int $amount ]) B<DEPRECATED>
448            
449             Prints out I<foo> and I<bar>
450            
451             B<Synopsis:>
452            
453             foobar(2); # prints two foos and two bars
454            
455             B<Parameters:>
456            
457             =over 4
458            
459             =item * Int C<<< $amount >>> (optional, defaults to C<<< 1 >>>)
460            
461             how many foo and bar should be printed
462            
463             =back
464            
465             This function is an example to show you a fancy way for its documentation
466            
467             B<Returns:>
468            
469             True on success
470            
471             B<Throws:>
472            
473             =over 4
474            
475             =item * An error message if there is no output device
476            
477             =back
478            
479             B<Available since:> 1.000
480            
481             B<DEPRECATION WARNING:>
482            
483             Use L</foobar_v2> instead.
484            
485             B<Author:> John Doe
486              
487             =head1 BUGS
488              
489             Please report any bugs or feature requests on the bugtracker website
490             https://github.com/zurborg/libmethod-signatures-withdocumentation-perl/issu
491             es
492              
493             When submitting a bug or request, please include a test-file or a
494             patch to an existing test-file that illustrates the bug or desired
495             feature.
496              
497             =head1 AUTHOR
498              
499             David Zurborg <zurborg@cpan.org>
500              
501             =head1 COPYRIGHT AND LICENSE
502              
503             This software is Copyright (c) 2015 by David Zurborg.
504              
505             This is free software, licensed under:
506              
507             The ISC License
508              
509             =cut