lib/PPI/Transform/Doxygen.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 286 | 288 | 99.3 |
branch | 88 | 108 | 81.4 |
condition | 38 | 51 | 74.5 |
subroutine | 30 | 30 | 100.0 |
pod | 3 | 3 | 100.0 |
total | 445 | 480 | 92.7 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package PPI::Transform::Doxygen; | ||||||
2 | |||||||
3 | =pod | ||||||
4 | |||||||
5 | =head1 NAME | ||||||
6 | |||||||
7 | PPI::Transform::Doxygen - PPI::Transform class for generating Doxygen input | ||||||
8 | |||||||
9 | =head1 SYNOPSIS | ||||||
10 | |||||||
11 | use PPI; | ||||||
12 | use PPI::Transform::Doxygen; | ||||||
13 | |||||||
14 | my $transform = PPI::Transform::Doxygen->new(); | ||||||
15 | |||||||
16 | # appends Doxygen Docs after __END__ (default when no output is given) | ||||||
17 | $transform->file('Module.pm'); | ||||||
18 | |||||||
19 | # prints Doxygen docs for use as a doxygen filter | ||||||
20 | $transform->file('Module.pm' => \*STDOUT); | ||||||
21 | |||||||
22 | =head1 DESCRIPTION | ||||||
23 | |||||||
24 | This module is normally used by the script L |
||||||
25 | part of this distribution and acts as a doxygen input filter (look for | ||||||
26 | B |
||||||
27 | |||||||
28 | There is already L |
||||||
29 | uses special doxygen comments. | ||||||
30 | |||||||
31 | The goal of PPI::Transform::Doxygen is to use only POD documentation with a | ||||||
32 | minimal amount of special syntax, while still producing decent results with | ||||||
33 | doxygen. | ||||||
34 | |||||||
35 | As doxygen is not able to parse perl directly, the input filter will | ||||||
36 | convert the source so that it will look like C++. | ||||||
37 | |||||||
38 | =head1 CONVENTIONS | ||||||
39 | |||||||
40 | The only thing really needed, is documenting your methods and functions with | ||||||
41 | a POD tag B<=head2> that contains a function string with parentheses ( it has | ||||||
42 | to match the regular expression /[\w:]+\(.*\)/) like so: | ||||||
43 | |||||||
44 | =head2 do_things() | ||||||
45 | |||||||
46 | This function does things | ||||||
47 | |||||||
48 | =cut | ||||||
49 | |||||||
50 | sub do_things { | ||||||
51 | print "Hi!\n"; | ||||||
52 | } | ||||||
53 | |||||||
54 | or so: | ||||||
55 | |||||||
56 | =head2 class_method $obj THINGY::new(%args) | ||||||
57 | |||||||
58 | Creates a new THINGY object | ||||||
59 | |||||||
60 | =cut | ||||||
61 | |||||||
62 | sub new { | ||||||
63 | my($class, %args) = @_; | ||||||
64 | return bless(\%args, $class); | ||||||
65 | } | ||||||
66 | |||||||
67 | |||||||
68 | All other POD documentation (including other =head2 tags) is added as HTML | ||||||
69 | (provided by Pod::POM::View::HTML) into the Doxygen section named | ||||||
70 | B |
||||||
71 | doxygen docs. Look under L on how to do that. | ||||||
72 | |||||||
73 | =head1 FUNCTION HEADERS | ||||||
74 | |||||||
75 | The complete syntax of a =head2 function description is: | ||||||
76 | |||||||
77 | C<< =head2 [ |
||||||
78 | |||||||
79 | =over | ||||||
80 | |||||||
81 | =item category (optional) | ||||||
82 | |||||||
83 | The category defines the type of the function definition. The values | ||||||
84 | C |
||||||
85 | as B |
||||||
86 | interpreting the function as method. | ||||||
87 | |||||||
88 | =item return_value (optional) | ||||||
89 | |||||||
90 | Since Doxygen expects C++ input, a return value is mandatory and will | ||||||
91 | default to B |
||||||
92 | careful with non word characters. | ||||||
93 | |||||||
94 | =item name | ||||||
95 | |||||||
96 | The function name with optional package name e.g. C |
||||||
97 | module will try to map the function name to the current package when none is | ||||||
98 | given. If your code is correctly parsable with PPI, then this should work. | ||||||
99 | |||||||
100 | If the corresponding subroutine is not found it will be tagged as B |
||||||
101 | to Doxygen. This is useful for dynamically generated functions (e.g via | ||||||
102 | AUTOLOAD). Yes this has nothing to do with the C++ virtual keyword, but so | ||||||
103 | what? If you want to have the virtual subroutine mapped to the correct | ||||||
104 | namespace you will have to add it to the subs name | ||||||
105 | (e.g. C< MyClass::mysub() >) | ||||||
106 | |||||||
107 | Subroutine names with leading underscore will be tagged as B |
||||||
108 | for Doxygen. | ||||||
109 | |||||||
110 | If there is no package declaration, the subroutine is created in the main | ||||||
111 | namespace, named C<< |
||||||
112 | |||||||
113 | =item parameters | ||||||
114 | |||||||
115 | The subroutine's comma separated parameter list. References are given in | ||||||
116 | dereference syntax so C<%$varname> specifies a hash reference. This will | ||||||
117 | be given as C |
||||||
118 | |||||||
119 | =back | ||||||
120 | |||||||
121 | =head1 SIGNATURES | ||||||
122 | |||||||
123 | If you are using subroutine signatures, they will be parsed for information | ||||||
124 | and you can put the pod after the sub declaration like so: | ||||||
125 | |||||||
126 | sub my_sig_sub ($self, $first = 'default', $second=[], %args) { | ||||||
127 | =for method $self | ||||||
128 | |||||||
129 | Sub documentation. | ||||||
130 | |||||||
131 | =cut | ||||||
132 | |||||||
133 | print join(' ', $first, @$second), "\n"; | ||||||
134 | return $self; | ||||||
135 | } | ||||||
136 | |||||||
137 | In that case there is no redundant information you'll have to synchronize on | ||||||
138 | each change. | ||||||
139 | In that case the first parameter behind the B<=for> has to be C |
||||||
140 | C |
||||||
141 | value. | ||||||
142 | A conflicting B<=head2> declaration for the same subroutine will take | ||||||
143 | precedence. | ||||||
144 | |||||||
145 | =head1 DETAILS ON TOP | ||||||
146 | |||||||
147 | For having the non subroutine POD documentation at the top of the Doxygen | ||||||
148 | page do the following: | ||||||
149 | |||||||
150 | =over | ||||||
151 | |||||||
152 | =item 1. | ||||||
153 | |||||||
154 | Create a doxygen layout XML file with C |
||||||
155 | |||||||
156 | =item 2. | ||||||
157 | |||||||
158 | Edit the XML file. Move C<< |
||||||
159 | line directly behind C<< |
||||||
160 | |||||||
161 | =item 3. | ||||||
162 | |||||||
163 | Specify the file under C |
||||||
164 | |||||||
165 | =back | ||||||
166 | |||||||
167 | =head1 METHODS | ||||||
168 | |||||||
169 | =cut | ||||||
170 | |||||||
171 | 2 | 2 | 360164 | use strict; | |||
2 | 11 | ||||||
2 | 83 | ||||||
172 | 2 | 2 | 14 | use warnings; | |||
2 | 5 | ||||||
2 | 69 | ||||||
173 | |||||||
174 | 2 | 2 | 1016 | use parent 'PPI::Transform'; | |||
2 | 595 | ||||||
2 | 11 | ||||||
175 | |||||||
176 | 2 | 2 | 3678 | use 5.010001; | |||
2 | 8 | ||||||
177 | 2 | 2 | 13 | use PPI; | |||
2 | 4 | ||||||
2 | 49 | ||||||
178 | 2 | 2 | 11 | use File::Basename qw(fileparse); | |||
2 | 4 | ||||||
2 | 151 | ||||||
179 | 2 | 2 | 1419 | use Pod::POM; | |||
2 | 38513 | ||||||
2 | 100 | ||||||
180 | 2 | 2 | 925 | use Pod::POM::View::Text; | |||
2 | 8449 | ||||||
2 | 59 | ||||||
181 | 2 | 2 | 745 | use PPI::Transform::Doxygen::POD; | |||
2 | 5 | ||||||
2 | 59 | ||||||
182 | 2 | 2 | 15 | use Params::Util qw{_INSTANCE}; | |||
2 | 3 | ||||||
2 | 3654 | ||||||
183 | |||||||
184 | our $VERSION = '0.32'; | ||||||
185 | |||||||
186 | my %vtype = qw(% hash @ array $ scalar & func * glob); | ||||||
187 | |||||||
188 | my %defaults = ( | ||||||
189 | rx_version => qr/our\s*\$VERSION\s*=\s*["']([\d\.]+)/, | ||||||
190 | rx_revision => qr/\$(?:Id|Rev|Revision|LastChangedRevision)\:\s*(\d+)\s*\$/, | ||||||
191 | rx_parent => qr/use\s+(?:base|parent|Mojo::Base)\s+["']?([\w:]+)["']?/, | ||||||
192 | ); | ||||||
193 | |||||||
194 | #================================================= | ||||||
195 | |||||||
196 | =head2 $obj new(%args) | ||||||
197 | |||||||
198 | B |
||||||
199 | |||||||
200 | There are 3 optional arguments for extracting a version number, a revision | ||||||
201 | number and the parent class. Their values have to consist of a regex with one | ||||||
202 | capture group. The key C< |
||||||
203 | output device on calling C< |
||||||
204 | doxygen docs after an __END__ Token. Setting overwrite to a true value will | ||||||
205 | overwrite the input file. | ||||||
206 | |||||||
207 | The defaults are: | ||||||
208 | |||||||
209 | rx_version => qr/our\s*\$VERSION\s*=\s*["']([\d\.]+)/, | ||||||
210 | rx_revision => qr/\$(?:Id|Rev|Revision|LastChangedRevision)\:\s*(\d+)\s*\$/, | ||||||
211 | rx_parent => qr/use\s+(?:base|parent|Mojo::Base)\s+["']?([\w:]+)["']?/, | ||||||
212 | overwrite => 0, | ||||||
213 | |||||||
214 | =cut | ||||||
215 | |||||||
216 | sub new { | ||||||
217 | 2 | 2 | 1 | 589 | my ( $class, %args ) = @_; | ||
218 | |||||||
219 | 2 | 24 | my $self = shift->SUPER::new(%defaults); | ||||
220 | |||||||
221 | 2 | 27 | @$self{ keys %args } = values %args; | ||||
222 | |||||||
223 | 2 | 7 | return $self; | ||||
224 | } | ||||||
225 | |||||||
226 | #================================================= | ||||||
227 | |||||||
228 | =head2 file($in, $out) | ||||||
229 | |||||||
230 | Start the transformation reading from C<$in> and saving to C<$out>. C<$in> | ||||||
231 | has to be a filename and C<$out> can be a filename or a filehandle. | ||||||
232 | If C<$out> is not given, behaviour is defined by the parameter overwrite | ||||||
233 | (see C |
||||||
234 | |||||||
235 | =cut | ||||||
236 | |||||||
237 | sub file { | ||||||
238 | 3 | 3 | 1 | 3622 | my ($self, $in, $out) = @_; | ||
239 | |||||||
240 | 3 | 50 | 15 | return unless $in; | |||
241 | |||||||
242 | 3 | 33 | 16 | my $preserve = !$out && !$self->{overwrite}; | |||
243 | |||||||
244 | 3 | 50 | 28 | my $Document = PPI::Document->new($in) or return undef; | |||
245 | 3 | 159370 | $Document->{_in_fn} = $in; | ||||
246 | 3 | 50 | 17 | $self->document($Document, $preserve) or return undef; | |||
247 | |||||||
248 | 3 | 33 | 155 | $out //= $in; | |||
249 | |||||||
250 | 3 | 50 | 14 | if ( ref($out) eq 'GLOB' ) { | |||
251 | 3 | 18 | print $out $Document->serialize(); | ||||
252 | } else { | ||||||
253 | 0 | 0 | $Document->save($out); | ||||
254 | } | ||||||
255 | } | ||||||
256 | |||||||
257 | #================================================= | ||||||
258 | |||||||
259 | =head2 document($ppi_doc, $preserve) | ||||||
260 | |||||||
261 | This is normally called by C |
||||||
262 | L |
||||||
263 | in place. | ||||||
264 | |||||||
265 | =cut | ||||||
266 | |||||||
267 | sub document { | ||||||
268 | 3 | 3 | 1 | 12 | my ( $self, $doc, $preserve ) = @_; | ||
269 | |||||||
270 | 3 | 50 | 27 | _INSTANCE( $doc, 'PPI::Document' ) or return undef; | |||
271 | |||||||
272 | 3 | 15 | my $pkg_subs = $self->_parse_packages_subs($doc); | ||||
273 | |||||||
274 | 3 | 161 | my($fname, $fdir, $fext) = fileparse( $doc->{_in_fn}, qr/\..*/ ); | ||||
275 | |||||||
276 | 3 | 22 | my($pod_txt, $sub_info) = $self->_parse_pod($doc, $fname); | ||||
277 | |||||||
278 | 3 | 17 | _integrate_sub_info($pkg_subs, $sub_info); | ||||
279 | |||||||
280 | 3 | 12 | my @packages = sort keys %$pkg_subs; | ||||
281 | 3 | 100 | 66 | 20 | my $file_pod = $pod_txt if @packages == 1 and $packages[0] eq '!main'; | ||
282 | |||||||
283 | 3 | 15 | my $dxout = _out_head($fname . $fext, $file_pod); | ||||
284 | |||||||
285 | 3 | 9 | for my $pname ( @packages ) { | ||||
286 | |||||||
287 | 3 | 11 | my @parts = split( /::/, $pname ); | ||||
288 | 3 | 6 | my $short = pop @parts; | ||||
289 | 3 | 100 | 21 | my $namespace = join( '::', @parts ) || ''; | |||
290 | |||||||
291 | $dxout .= _out_class_begin( | ||||||
292 | $pname, $short, $namespace, $fname, | ||||||
293 | $pkg_subs->{$pname}{inherit}, | ||||||
294 | $pkg_subs->{$pname}{used}, | ||||||
295 | $pkg_subs->{$pname}{version}, | ||||||
296 | $pkg_subs->{$pname}{revision}, | ||||||
297 | 3 | 100 | 21 | $short eq $fname ? $pod_txt : '', | |||
298 | ); | ||||||
299 | |||||||
300 | 3 | 24 | $dxout .= _out_process_subs( $pname, $pkg_subs, $sub_info ); | ||||
301 | |||||||
302 | 3 | 22 | $dxout .= _out_class_end($namespace); | ||||
303 | } | ||||||
304 | |||||||
305 | 3 | 50 | 10 | unless ($preserve) { | |||
306 | 3 | 18 | $_->delete for $doc->children(); | ||||
307 | } | ||||||
308 | |||||||
309 | 3 | 33 | 12200 | my $end_tok = $doc->find_first('PPI::Token::End') || PPI::Token::End->new(); | |||
310 | 3 | 1149 | $end_tok->add_content($dxout); | ||||
311 | 3 | 43 | $doc->add_element($end_tok); | ||||
312 | } | ||||||
313 | |||||||
314 | |||||||
315 | 39 | 39 | 53 | sub _strip { my $str = shift; $str =~ s/^ +//mg; $str } | |||
39 | 257 | ||||||
39 | 70 | ||||||
316 | |||||||
317 | |||||||
318 | sub _out_head { | ||||||
319 | 3 | 3 | 8 | my($fn, $txt) = @_; | |||
320 | |||||||
321 | 3 | 100 | 10 | $txt //= ''; | |||
322 | 3 | 16 | my $out = _strip(qq( | ||||
323 | /** \@file $fn | ||||||
324 | $txt | ||||||
325 | */ | ||||||
326 | )); | ||||||
327 | |||||||
328 | 3 | 6 | return $out; | ||||
329 | } | ||||||
330 | |||||||
331 | |||||||
332 | sub _get_used_modules { | ||||||
333 | 3 | 3 | 9 | my($root) = @_; | |||
334 | |||||||
335 | 3 | 7 | my %used; | ||||
336 | 3 | 18 | for my $chld ( $root->schildren() ) { | ||||
337 | 47 | 100 | 486 | next unless $chld->isa('PPI::Statement::Include'); | |||
338 | 10 | 100 | 25 | next if $chld->pragma(); | |||
339 | 6 | 168 | $used{$chld->module()} = 1 | ||||
340 | } | ||||||
341 | 3 | 15 | return \%used; | ||||
342 | } | ||||||
343 | |||||||
344 | |||||||
345 | sub _parse_packages_subs { | ||||||
346 | 3 | 3 | 9 | my($self, $doc) = @_; | |||
347 | |||||||
348 | 3 | 7 | my %pkg_subs; | ||||
349 | |||||||
350 | my @main_pkgs = grep { | ||||||
351 | 3 | 21 | $_->isa('PPI::Statement::Package') | ||||
141 | 332 | ||||||
352 | } $doc->children(); | ||||||
353 | |||||||
354 | 3 | 100 | 22 | unless (@main_pkgs) { | |||
355 | 2 | 11 | $pkg_subs{'!main'}{used} = _get_used_modules($doc); | ||||
356 | 2 | 9 | my($v, $r) = $self->_get_pkg_version($doc); | ||||
357 | 2 | 18 | $pkg_subs{'!main'}{version} = $v; | ||||
358 | 2 | 6 | $pkg_subs{'!main'}{revision} = $r; | ||||
359 | } | ||||||
360 | |||||||
361 | 3 | 50 | 26 | my $stmt_nodes = $doc->find('PPI::Statement') || []; | |||
362 | 3 | 33072 | for my $stmt_node ( @$stmt_nodes ) { | ||||
363 | |||||||
364 | 141 | 2326 | my $pkg = '!main'; | ||||
365 | 141 | 100 | 100 | 327 | next unless $stmt_node->class() eq 'PPI::Statement::Sub' | ||
366 | or $stmt_node->child(0) eq 'has'; | ||||||
367 | |||||||
368 | 14 | 126 | my $node = $stmt_node; | ||||
369 | 14 | 35 | while ($node) { | ||||
370 | 662 | 100 | 27734 | if ( $node->class() eq 'PPI::Statement::Package' ) { | |||
371 | 8 | 36 | $pkg = $node->namespace(); | ||||
372 | 8 | 50 | 175 | unless ( $pkg_subs{$pkg}{version} ) { | |||
373 | 8 | 18 | my($v, $r) = $self->_get_pkg_version($node->parent()); | ||||
374 | 8 | 13 | $pkg_subs{$pkg}{version} = $v; | ||||
375 | 8 | 14 | $pkg_subs{$pkg}{revision} = $r; | ||||
376 | } | ||||||
377 | 8 | 100 | 17 | unless ( defined $pkg_subs{$pkg}{inherit} ) { | |||
378 | my ($inherit) = _find_first_regex( | ||||||
379 | $node->parent(), | ||||||
380 | 'PPI::Statement::Include', | ||||||
381 | $self->{rx_parent}, | ||||||
382 | 1 | 3 | ); | ||||
383 | 1 | 2 | $pkg_subs{$pkg}{inherit} = $inherit; | ||||
384 | } | ||||||
385 | 8 | 100 | 17 | unless ( defined $pkg_subs{$pkg}{used} ) { | |||
386 | 1 | 3 | my $parent = $node->parent(); | ||||
387 | 1 | 50 | 16 | $pkg_subs{$pkg}{used} = _get_used_modules($parent) | |||
388 | if $parent; | ||||||
389 | } | ||||||
390 | } | ||||||
391 | 662 | 100 | 2314 | $node = $node->previous_sibling() || $node->parent(); | |||
392 | } | ||||||
393 | |||||||
394 | 14 | 100 | 131 | my $sub_name = $stmt_node->class() eq 'PPI::Statement::Sub' | |||
395 | ? $stmt_node->name | ||||||
396 | : $stmt_node->child(2)->content; | ||||||
397 | |||||||
398 | 14 | 100 | 466 | for my $sn ( grep { /\w/ && $_ ne 'qw' } split(/\W+/, $sub_name) ) { | |||
20 | 122 | ||||||
399 | 17 | 65 | $pkg_subs{$pkg}{subs}{ $sn } = $stmt_node; | ||||
400 | } | ||||||
401 | } | ||||||
402 | |||||||
403 | 3 | 72 | return \%pkg_subs; | ||||
404 | } | ||||||
405 | |||||||
406 | |||||||
407 | sub _out_process_subs { | ||||||
408 | 3 | 3 | 10 | my($class, $pkg_subs, $sub_info) = @_; | |||
409 | |||||||
410 | 3 | 7 | my $sub_nodes = $pkg_subs->{$class}{subs}; | ||||
411 | |||||||
412 | 3 | 5 | my $out = ''; | ||||
413 | |||||||
414 | 3 | 6 | my %types; | ||||
415 | 3 | 14 | for my $sname ( sort keys %$sub_nodes ) { | ||||
416 | 18 | 100 | 58 | my $si = $sub_info->{$sname} || { | |||
417 | type => $sname =~ /^_/ ? 'private' : 'public', | ||||||
418 | rv => 'void', | ||||||
419 | params => [], | ||||||
420 | name => $sname, | ||||||
421 | static => 0, | ||||||
422 | virtual => 0, | ||||||
423 | class => $class, | ||||||
424 | text => ' Undocumented Function ', |
||||||
425 | }; | ||||||
426 | 18 | 49 | $types{ $si->{type} }{$sname} = $si; | ||||
427 | } | ||||||
428 | |||||||
429 | 3 | 16 | for my $type (qw/public private/) { | ||||
430 | 6 | 14 | $out .= "$type:\n"; | ||||
431 | 6 | 9 | for my $sname ( sort keys %{ $types{$type} } ) { | ||||
6 | 32 | ||||||
432 | 18 | 39 | my $si = $types{$type}{$sname}; | ||||
433 | 18 | 100 | 47 | my @static = $si->{static} ? 'static' : (); | |||
434 | 18 | 100 | 31 | my @virtual = $si->{virtual} ? 'virtual' : (); | |||
435 | |||||||
436 | 18 | 37 | my $fstr = join( ' ', @static, @virtual, $si->{rv}, "$sname(" ); | ||||
437 | 18 | 20 | $fstr .= join( ', ', @{ $si->{params} } ); | ||||
18 | 33 | ||||||
438 | 18 | 24 | $fstr .= ')'; | ||||
439 | |||||||
440 | 18 | 29 | $out .= "/** \@fn $fstr\n"; | ||||
441 | 18 | 28 | $out .= $si->{text} . "\n"; | ||||
442 | 18 | 32 | $out .= _out_html_code( $sname, $sub_nodes->{$sname} ); | ||||
443 | 18 | 28 | $out .= "*/\n"; | ||||
444 | 18 | 44 | $out .= $fstr . ";\n\n"; | ||||
445 | } | ||||||
446 | } | ||||||
447 | |||||||
448 | 3 | 56 | return $out; | ||||
449 | } | ||||||
450 | |||||||
451 | |||||||
452 | sub _out_class_begin { | ||||||
453 | 3 | 3 | 15 | my($pname, $pkg_short, $namespace, $fname, $inherit, $used, $ver, $rev, $pod_txt) = @_; | |||
454 | |||||||
455 | 3 | 100 | 10 | if ( $pname eq '!main' ) { | |||
456 | 2 | 5 | $pkg_short = $pname = "${fname}_main"; | ||||
457 | } | ||||||
458 | |||||||
459 | 3 | 5 | my $out = ''; | ||||
460 | |||||||
461 | 3 | 100 | 10 | $out .= "namespace $namespace {\n" if $namespace; | |||
462 | |||||||
463 | 3 | 8 | $out .= "\n/** \@class $pname\n\n"; | ||||
464 | 3 | 50 | 18 | $out .= "\@version $ver" if $ver; | |||
465 | 3 | 50 | 10 | $out .= " rev:$rev" if $rev; | |||
466 | 3 | 5 | $out .= "\n\n"; | ||||
467 | |||||||
468 | 3 | 50 | 8 | if ($used) { | |||
469 | 3 | 9 | $out .= "\@section ${pkg_short}_USED_MODULES USED_MODULES\n"; | ||||
470 | 3 | 5 | $out .= "
|
||||
471 | 3 | 15 | for my $uname ( sort keys %$used ) { | ||||
472 | 6 | 16 | $out .= " |
||||
473 | } | ||||||
474 | 3 | 5 | $out .= "\n"; | ||||
475 | } | ||||||
476 | |||||||
477 | 3 | 8 | $out .= "$pod_txt\n*/\n\n"; | ||||
478 | |||||||
479 | 3 | 7 | $out .= "class $pkg_short: public"; | ||||
480 | 3 | 50 | 8 | $out .= " ::$inherit" if $inherit; | |||
481 | 3 | 6 | $out .= " {\n\n"; | ||||
482 | |||||||
483 | 3 | 8 | return $out; | ||||
484 | } | ||||||
485 | |||||||
486 | |||||||
487 | sub _out_class_end { | ||||||
488 | 3 | 3 | 9 | my($namespace) = @_; | |||
489 | |||||||
490 | 3 | 7 | my $out = "};\n"; | ||||
491 | 3 | 100 | 14 | $out .= "};\n" if $namespace; | |||
492 | |||||||
493 | 3 | 12 | return $out; | ||||
494 | } | ||||||
495 | |||||||
496 | |||||||
497 | sub _parse_pod { | ||||||
498 | 3 | 3 | 14 | my($self, $doc, $fname) = @_; | |||
499 | |||||||
500 | 3 | 53 | my $parser = Pod::POM->new(); | ||||
501 | |||||||
502 | 3 | 75 | my $txt = ''; | ||||
503 | 3 | 6 | my %subs; | ||||
504 | |||||||
505 | 3 | 13 | my $pod_tokens = $doc->find('PPI::Token::Pod'); | ||||
506 | |||||||
507 | 3 | 100 | 32127 | return '', \%subs unless $pod_tokens; | |||
508 | |||||||
509 | 2 | 2 | 16 | no warnings qw(once); | |||
2 | 3 | ||||||
2 | 3334 | ||||||
510 | 2 | 8 | $PPI::Transform::Doxygen::POD::PREFIX = $fname; | ||||
511 | 2 | 5 | for my $tok ( @$pod_tokens ) { | ||||
512 | 15 | 13099 | ( my $quoted = $tok->content() ) =~ s/(\@|\\|\%|#)/\\$1/g; | ||||
513 | 15 | 145 | my $pom = $parser->parse_text($quoted); | ||||
514 | 15 | 6491 | _filter_head2( $pom, \%subs ); | ||||
515 | 15 | 113 | $txt .= PPI::Transform::Doxygen::POD->print($pom); | ||||
516 | } | ||||||
517 | |||||||
518 | 2 | 485 | return $txt, \%subs; | ||||
519 | } | ||||||
520 | |||||||
521 | |||||||
522 | sub _filter_head2 { | ||||||
523 | 19 | 19 | 38 | my($pom, $sub_ref) = @_; | |||
524 | |||||||
525 | 19 | 86 | my $nodes = $pom->content(); | ||||
526 | 19 | 251 | my $method_for = 0; | ||||
527 | 19 | 35 | for my $sn ( @$nodes ) { | ||||
528 | 25 | 100 | 90 | $sn = '' if $method_for; | |||
529 | 25 | 100 | 100 | 97 | next unless $sn and $sn->type() =~ /^(?:head[1-4]|begin|item|over|pod|for)$/; | ||
530 | 16 | 100 | 100 | 463 | if ( $sn->type() eq 'head2' and $sn->title() =~ /[\w:]+\s*\(.*\)/ ) { | ||
100 | |||||||
531 | 8 | 636 | my $sinfo = _sub_extract( $sn->title() ); | ||||
532 | 8 | 50 | 21 | if ($sinfo) { | |||
533 | 8 | 44 | $sinfo->{text} = PPI::Transform::Doxygen::POD->print($sn->content()); | ||||
534 | 8 | 725 | $sub_ref->{$sinfo->{name}} = $sinfo; | ||||
535 | 8 | 27 | $sn = ''; | ||||
536 | } | ||||||
537 | } elsif ( $sn->type() eq 'for' ) { | ||||||
538 | 4 | 50 | 33 | 114 | if ( | ||
539 | $sn->type eq 'for' | ||||||
540 | and | ||||||
541 | $sn->format =~ /^(?:function|method|class_method)$/ | ||||||
542 | ) { | ||||||
543 | 4 | 126 | $sn = ''; | ||||
544 | 4 | 7 | $method_for = 1; | ||||
545 | } | ||||||
546 | |||||||
547 | } else { | ||||||
548 | 4 | 163 | _filter_head2($sn); | ||||
549 | } | ||||||
550 | } | ||||||
551 | } | ||||||
552 | |||||||
553 | |||||||
554 | my $rx_name_parms = qr/\s*([\w:]+)\s*\(\s*([^\)]*)\s*\)$/; | ||||||
555 | sub _sub_extract { | ||||||
556 | 8 | 8 | 124 | my($str) = @_; | |||
557 | |||||||
558 | |||||||
559 | 8 | 44 | my($long, $params) = $str =~ /$rx_name_parms/; | ||||
560 | 8 | 50 | 399 | return unless $long; | |||
561 | |||||||
562 | 8 | 36 | $str =~ s/$rx_name_parms//; | ||||
563 | |||||||
564 | 8 | 630 | my @parts = split(/\s+/, $str); | ||||
565 | |||||||
566 | 8 | 100 | 25 | my $rv = pop(@parts) || 'void'; | |||
567 | 8 | 22 | $rv =~ s/(\%|\@|\&)/\\$1/g; | ||||
568 | |||||||
569 | 8 | 100 | 34 | my $cat = pop(@parts) || ''; | |||
570 | |||||||
571 | 8 | 20 | my @params = _add_type($params); | ||||
572 | |||||||
573 | 8 | 20 | my @nparts = split( /::/, $long ); | ||||
574 | 8 | 15 | my $name = pop @nparts; | ||||
575 | 8 | 50 | 29 | my $class = join( '::', @nparts ) || '!main'; | |||
576 | |||||||
577 | 8 | 100 | 25 | my $static = $cat eq 'function' || $cat eq 'class_method'; | |||
578 | 8 | 100 | 28 | my $type = $name =~ /^_/ ? 'private' : 'public'; | |||
579 | |||||||
580 | return { | ||||||
581 | 8 | 51 | type => $type, | ||||
582 | rv => $rv, | ||||||
583 | params => \@params, | ||||||
584 | name => $name, | ||||||
585 | static => $static, | ||||||
586 | class => $class, | ||||||
587 | }; | ||||||
588 | } | ||||||
589 | |||||||
590 | |||||||
591 | sub _add_type { | ||||||
592 | 11 | 100 | 11 | 22 | return unless my $params = shift; | ||
593 | |||||||
594 | 10 | 100 | 21 | unless ( ref($params) ) { | |||
595 | 7 | 16 | $params =~ s/\s//g; | ||||
596 | 7 | 19 | $params = [ split(/,/, $params) ]; | ||||
597 | } | ||||||
598 | |||||||
599 | return map { | ||||||
600 | 10 | 20 | my @sig = $_ =~ /^(.)(.)(.?)/; | ||||
17 | 54 | ||||||
601 | 17 | 100 | 36 | if ( $sig[0] eq '\\' ) { shift @sig } | |||
4 | 6 | ||||||
602 | 17 | 20 | my $ref; | ||||
603 | 17 | 100 | 29 | if ( $sig[1] eq '$' ) { $ref = 1; splice(@sig, 1, 1) } | |||
2 | 3 | ||||||
2 | 4 | ||||||
604 | 17 | 47 | my $typ = $vtype{ $sig[0] }; | ||||
605 | 17 | 100 | 38 | $typ .= '_ref' if $ref; | |||
606 | 17 | 54 | s/^\W*//; | ||||
607 | 17 | 62 | $_ = "$typ $_"; | ||||
608 | } @$params; | ||||||
609 | } | ||||||
610 | |||||||
611 | |||||||
612 | sub _find_first_regex { | ||||||
613 | 21 | 21 | 39 | my($root, $name, $regex) = @_; | |||
614 | 21 | 49 | for my $chld ( $root->schildren() ) { | ||||
615 | 244 | 100 | 3208 | next unless $chld->isa($name); | |||
616 | 15 | 50 | 34 | if ( my @capture = $chld->content() =~ /$regex/ ) { | |||
617 | 0 | 0 | return @capture; | ||||
618 | } | ||||||
619 | } | ||||||
620 | 21 | 45 | return ''; | ||||
621 | } | ||||||
622 | |||||||
623 | |||||||
624 | sub _get_pkg_version { | ||||||
625 | 10 | 10 | 41 | my($self, $root) = @_; | |||
626 | my($version) = _find_first_regex( | ||||||
627 | $root, | ||||||
628 | 'PPI::Statement::Variable', | ||||||
629 | $self->{rx_version}, | ||||||
630 | 10 | 31 | ); | ||||
631 | |||||||
632 | my($revision) = _find_first_regex( | ||||||
633 | $root, | ||||||
634 | 'PPI::Statement::Variable', | ||||||
635 | $self->{rx_revision}, | ||||||
636 | 10 | 22 | ); | ||||
637 | 10 | 25 | return $version, $revision; | ||||
638 | } | ||||||
639 | |||||||
640 | |||||||
641 | sub _out_html_code { | ||||||
642 | 18 | 18 | 29 | my($sname, $sub) = @_; | |||
643 | |||||||
644 | 18 | 58 | my $html = _strip(qq( | ||||
645 | \@htmlonly | ||||||
646 | |
||||||
647 | ![]() |
||||||
648 | |||||||
649 | click to view |
||||||
650 | |||||||
651 | \@endhtmlonly | ||||||
652 | \@code | ||||||
653 | )); | ||||||
654 | |||||||
655 | 18 | 53 | $html .= $sub; | ||||
656 | 18 | 1454 | $html .= "\n"; | ||||
657 | |||||||
658 | 18 | 30 | $html .= _strip(q( | ||||
659 | @endcode | ||||||
660 | @htmlonly | ||||||
661 | |||||||
662 | @endhtmlonly | ||||||
663 | )); | ||||||
664 | |||||||
665 | 18 | 65 | return $html; | ||||
666 | } | ||||||
667 | |||||||
668 | |||||||
669 | sub _sub_info_from_node { | ||||||
670 | 10 | 10 | 19 | my($sname, $class, $node) = @_; | |||
671 | |||||||
672 | 10 | 100 | 23 | return undef unless $node->class eq 'PPI::Statement::Sub'; | |||
673 | |||||||
674 | 3 | 18 | my $parser = Pod::POM->new(); | ||||
675 | 3 | 30 | my %si; | ||||
676 | 3 | 4 | my $txt = my $def = ''; | ||||
677 | 3 | 4 | my @params; | ||||
678 | 3 | 5 | my($rv, $static); | ||||
679 | 3 | 50 | 8 | my $type = $sname =~ /^_/ ? 'private' : 'public'; | |||
680 | |||||||
681 | 3 | 50 | 16 | my $pod = $node->find('PPI::Token::Pod') || []; | |||
682 | 3 | 3182 | for my $tok ( @$pod ) { | ||||
683 | 3 | 10 | ( my $quoted = $tok ) =~ s/(\@|\\|\%|#)/\\$1/g; | ||||
684 | 3 | 20 | my $pom = $parser->parse_text($quoted); | ||||
685 | 3 | 50 | 1042 | next unless my $for = $pom->for->[0]; | |||
686 | 3 | 59 | $rv = $for->text; | ||||
687 | 3 | 100 | 41 | $static = $for->format eq 'function' || $for->format eq 'class_method'; | |||
688 | 3 | 66 | $txt .= PPI::Transform::Doxygen::POD->print($pom); | ||||
689 | } | ||||||
690 | 3 | 50 | 893 | my $proto = $node->find('PPI::Token::Prototype') || []; | |||
691 | 3 | 2932 | for my $tok ( @$proto ) { | ||||
692 | 3 | 12 | for my $pmt ( split(/,/, $tok->prototype) ) { | ||||
693 | 9 | 75 | my($attr, $default) = split(/=/, $pmt); | ||||
694 | 9 | 15 | push @params, $attr; | ||||
695 | 9 | 100 | 17 | next unless $default; | |||
696 | 3 | 10 | $def .= " Default value for $attr is $default. \n"; |
||||
697 | } | ||||||
698 | 3 | 7 | @params = _add_type(\@params); | ||||
699 | } | ||||||
700 | 3 | 7 | my @word_tok = $node->find('PPI::Token::Word'); | ||||
701 | 3 | 2981 | my $last; | ||||
702 | 3 | 9 | while ( my $tok = pop @word_tok ) { | ||||
703 | 3 | 6 | $last = "$tok"; | ||||
704 | 3 | 50 | 13 | next unless $tok eq 'return'; | |||
705 | } | ||||||
706 | |||||||
707 | 3 | 50 | 7 | return undef unless $txt; | |||
708 | |||||||
709 | 3 | 100 | 9 | $txt .= "\n$def" if $def; | |||
710 | |||||||
711 | return { | ||||||
712 | 3 | 24 | type => $type, | ||||
713 | rv => $rv, | ||||||
714 | params => \@params, | ||||||
715 | name => $sname, | ||||||
716 | static => $static, | ||||||
717 | class => $class, | ||||||
718 | text => $txt, | ||||||
719 | } | ||||||
720 | } | ||||||
721 | |||||||
722 | |||||||
723 | sub _integrate_sub_info { | ||||||
724 | 3 | 3 | 8 | my($pkg_subs, $sub_info) = @_; | |||
725 | |||||||
726 | 3 | 12 | my %si_by_name = map { $_ => $sub_info->{$_} } keys %$sub_info; | ||||
8 | 18 | ||||||
727 | |||||||
728 | 3 | 6 | my %look; | ||||
729 | 3 | 12 | for my $class ( keys %$pkg_subs ) { | ||||
730 | 3 | 6 | for my $subname ( keys %{ $pkg_subs->{$class}{subs} } ) { | ||||
3 | 14 | ||||||
731 | 17 | 100 | 30 | if ( $si_by_name{$subname} ) { | |||
732 | # pod info exists | ||||||
733 | 7 | 13 | $si_by_name{$subname}{class} = $class; | ||||
734 | 7 | 8 | $look{$subname} = 1; | ||||
735 | 7 | 12 | next; | ||||
736 | }; | ||||||
737 | my $si = _sub_info_from_node( | ||||||
738 | $subname, | ||||||
739 | $class, | ||||||
740 | 10 | 18 | $pkg_subs->{$class}{subs}{$subname}, | ||||
741 | ); | ||||||
742 | 10 | 100 | 49 | $sub_info->{$subname} = $si if $si; | |||
743 | 10 | 21 | $look{$subname} = 1; | ||||
744 | } | ||||||
745 | } | ||||||
746 | |||||||
747 | 3 | 11 | for my $si ( values %$sub_info ) { | ||||
748 | 11 | 100 | 27 | next if $look{ $si->{name} }; | |||
749 | 1 | 3 | $si->{virtual} = 1; | ||||
750 | $pkg_subs->{$si->{class}}{subs}{$si->{name}} | ||||||
751 | 1 | 6 | = ' virtual function or method '; |
||||
752 | } | ||||||
753 | } | ||||||
754 | |||||||
755 | 1; | ||||||
756 | |||||||
757 | =pod | ||||||
758 | |||||||
759 | =head1 AUTHOR | ||||||
760 | |||||||
761 | Thomas Kratz E |
||||||
762 | |||||||
763 | =head1 REPOSITORY | ||||||
764 | |||||||
765 | L |
||||||
766 | |||||||
767 | =head1 COPYRIGHT | ||||||
768 | |||||||
769 | Copyright 2016 Thomas Kratz. | ||||||
770 | |||||||
771 | This program is free software; you can redistribute | ||||||
772 | it and/or modify it under the same terms as Perl itself. | ||||||
773 | |||||||
774 | =cut |