lib/PPI/Transform/Doxygen.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 280 | 282 | 99.2 |
branch | 87 | 104 | 83.6 |
condition | 43 | 53 | 81.1 |
subroutine | 30 | 30 | 100.0 |
pod | 3 | 3 | 100.0 |
total | 443 | 472 | 93.8 |
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. Both parameters must be present because the =for tag requires them. | ||||||
142 | There are no defaults. | ||||||
143 | |||||||
144 | A conflicting B<=head2> declaration for the same subroutine will take | ||||||
145 | precedence. | ||||||
146 | |||||||
147 | =head1 DETAILS ON TOP | ||||||
148 | |||||||
149 | For having the non subroutine POD documentation at the top of the Doxygen | ||||||
150 | page do the following: | ||||||
151 | |||||||
152 | =over | ||||||
153 | |||||||
154 | =item 1. | ||||||
155 | |||||||
156 | Create a doxygen layout XML file with C |
||||||
157 | |||||||
158 | =item 2. | ||||||
159 | |||||||
160 | Edit the XML file. Move C<< |
||||||
161 | line directly behind C<< |
||||||
162 | |||||||
163 | =item 3. | ||||||
164 | |||||||
165 | Specify the file under C |
||||||
166 | |||||||
167 | =back | ||||||
168 | |||||||
169 | =head1 METHODS | ||||||
170 | |||||||
171 | =cut | ||||||
172 | |||||||
173 | 2 | 2 | 318039 | use strict; | |||
2 | 10 | ||||||
2 | 50 | ||||||
174 | 2 | 2 | 9 | use warnings; | |||
2 | 4 | ||||||
2 | 56 | ||||||
175 | |||||||
176 | 2 | 2 | 773 | use parent 'PPI::Transform'; | |||
2 | 474 | ||||||
2 | 9 | ||||||
177 | |||||||
178 | 2 | 2 | 2522 | use 5.010001; | |||
2 | 6 | ||||||
179 | 2 | 2 | 9 | use PPI; | |||
2 | 4 | ||||||
2 | 34 | ||||||
180 | 2 | 2 | 9 | use File::Basename qw(fileparse); | |||
2 | 3 | ||||||
2 | 117 | ||||||
181 | 2 | 2 | 1058 | use Pod::POM; | |||
2 | 34757 | ||||||
2 | 98 | ||||||
182 | 2 | 2 | 849 | use Pod::POM::View::Text; | |||
2 | 7912 | ||||||
2 | 66 | ||||||
183 | 2 | 2 | 733 | use PPI::Transform::Doxygen::POD; | |||
2 | 5 | ||||||
2 | 61 | ||||||
184 | 2 | 2 | 12 | use Params::Util qw{_INSTANCE}; | |||
2 | 4 | ||||||
2 | 3431 | ||||||
185 | |||||||
186 | our $VERSION = '0.33'; | ||||||
187 | |||||||
188 | my %vtype = qw(% hash @ array $ scalar & func * glob); | ||||||
189 | |||||||
190 | my %defaults = ( | ||||||
191 | rx_version => qr/our\s*\$VERSION\s*=\s*["']([\d\.]+)/, | ||||||
192 | rx_revision => qr/\$(?:Id|Rev|Revision|LastChangedRevision)\:\s*(\d+)\s*\$/, | ||||||
193 | rx_parent => qr/use\s+(?:base|parent|Mojo::Base)\s+["']?([\w:]+)["']?/, | ||||||
194 | ); | ||||||
195 | |||||||
196 | #================================================= | ||||||
197 | |||||||
198 | =head2 $obj new(%args) | ||||||
199 | |||||||
200 | B |
||||||
201 | |||||||
202 | There are 3 optional arguments for extracting a version number, a revision | ||||||
203 | number and the parent class. Their values have to consist of a regex with one | ||||||
204 | capture group. The key C< |
||||||
205 | output device on calling C< |
||||||
206 | doxygen docs after an __END__ Token. Setting overwrite to a true value will | ||||||
207 | overwrite the input file. | ||||||
208 | |||||||
209 | The defaults are: | ||||||
210 | |||||||
211 | rx_version => qr/our\s*\$VERSION\s*=\s*["']([\d\.]+)/, | ||||||
212 | rx_revision => qr/\$(?:Id|Rev|Revision|LastChangedRevision)\:\s*(\d+)\s*\$/, | ||||||
213 | rx_parent => qr/use\s+(?:base|parent|Mojo::Base)\s+["']?([\w:]+)["']?/, | ||||||
214 | overwrite => 0, | ||||||
215 | |||||||
216 | =cut | ||||||
217 | |||||||
218 | sub new { | ||||||
219 | 2 | 2 | 1 | 271 | my ( $class, %args ) = @_; | ||
220 | |||||||
221 | 2 | 22 | my $self = shift->SUPER::new(%defaults); | ||||
222 | |||||||
223 | 2 | 22 | @$self{ keys %args } = values %args; | ||||
224 | |||||||
225 | 2 | 6 | return $self; | ||||
226 | } | ||||||
227 | |||||||
228 | #================================================= | ||||||
229 | |||||||
230 | =head2 file($in, $out) | ||||||
231 | |||||||
232 | Start the transformation reading from C<$in> and saving to C<$out>. C<$in> | ||||||
233 | has to be a filename and C<$out> can be a filename or a filehandle. | ||||||
234 | If C<$out> is not given, behaviour is defined by the parameter overwrite | ||||||
235 | (see C |
||||||
236 | |||||||
237 | =cut | ||||||
238 | |||||||
239 | sub file { | ||||||
240 | 3 | 3 | 1 | 3116 | my ($self, $in, $out) = @_; | ||
241 | |||||||
242 | 3 | 50 | 12 | return unless $in; | |||
243 | |||||||
244 | 3 | 33 | 11 | my $preserve = !$out && !$self->{overwrite}; | |||
245 | |||||||
246 | 3 | 50 | 23 | my $Document = PPI::Document->new($in) or return undef; | |||
247 | 3 | 148366 | $Document->{_in_fn} = $in; | ||||
248 | 3 | 50 | 18 | $self->document($Document, $preserve) or return undef; | |||
249 | |||||||
250 | 3 | 33 | 175 | $out //= $in; | |||
251 | |||||||
252 | 3 | 50 | 30 | if ( ref($out) eq 'GLOB' ) { | |||
253 | 3 | 19 | print $out $Document->serialize(); | ||||
254 | } else { | ||||||
255 | 0 | 0 | $Document->save($out); | ||||
256 | } | ||||||
257 | } | ||||||
258 | |||||||
259 | #================================================= | ||||||
260 | |||||||
261 | =head2 document($ppi_doc, $preserve) | ||||||
262 | |||||||
263 | This is normally called by C |
||||||
264 | L |
||||||
265 | in place. | ||||||
266 | |||||||
267 | =cut | ||||||
268 | |||||||
269 | sub document { | ||||||
270 | 3 | 3 | 1 | 9 | my ( $self, $doc, $preserve ) = @_; | ||
271 | |||||||
272 | 3 | 50 | 25 | _INSTANCE( $doc, 'PPI::Document' ) or return undef; | |||
273 | |||||||
274 | 3 | 16 | my $pkg_subs = $self->_parse_packages_subs($doc); | ||||
275 | |||||||
276 | 3 | 201 | my($fname, $fdir, $fext) = fileparse( $doc->{_in_fn}, qr/\..*/ ); | ||||
277 | |||||||
278 | 3 | 25 | my($pod_txt, $sub_info) = $self->_parse_pod($doc, $fname); | ||||
279 | |||||||
280 | 3 | 17 | _integrate_sub_info($pkg_subs, $sub_info); | ||||
281 | |||||||
282 | 3 | 12 | my @packages = sort keys %$pkg_subs; | ||||
283 | 3 | 100 | 66 | 22 | my $file_pod = $pod_txt if @packages == 1 and $packages[0] eq '!main'; | ||
284 | |||||||
285 | 3 | 15 | my $dxout = _out_head($fname . $fext, $file_pod); | ||||
286 | |||||||
287 | 3 | 38 | for my $pname ( @packages ) { | ||||
288 | |||||||
289 | 3 | 14 | my @parts = split( /::/, $pname ); | ||||
290 | 3 | 7 | my $short = pop @parts; | ||||
291 | 3 | 100 | 18 | my $namespace = join( '::', @parts ) || ''; | |||
292 | |||||||
293 | $dxout .= _out_class_begin( | ||||||
294 | $pname, $short, $namespace, $fname, | ||||||
295 | $pkg_subs->{$pname}{inherit}, | ||||||
296 | $pkg_subs->{$pname}{used}, | ||||||
297 | $pkg_subs->{$pname}{version}, | ||||||
298 | $pkg_subs->{$pname}{revision}, | ||||||
299 | 3 | 100 | 26 | $short eq $fname ? $pod_txt : '', | |||
300 | ); | ||||||
301 | |||||||
302 | 3 | 14 | $dxout .= _out_process_subs( $pname, $pkg_subs, $sub_info ); | ||||
303 | |||||||
304 | 3 | 19 | $dxout .= _out_class_end($namespace); | ||||
305 | } | ||||||
306 | |||||||
307 | 3 | 50 | 11 | unless ($preserve) { | |||
308 | 3 | 17 | $_->delete for $doc->children(); | ||||
309 | } | ||||||
310 | |||||||
311 | 3 | 33 | 11317 | my $end_tok = $doc->find_first('PPI::Token::End') || PPI::Token::End->new(); | |||
312 | 3 | 1154 | $end_tok->add_content($dxout); | ||||
313 | 3 | 44 | $doc->add_element($end_tok); | ||||
314 | } | ||||||
315 | |||||||
316 | |||||||
317 | 49 | 49 | 59 | sub _strip { my $str = shift; $str =~ s/^ +//mg; $str } | |||
49 | 306 | ||||||
49 | 84 | ||||||
318 | |||||||
319 | |||||||
320 | sub _out_head { | ||||||
321 | 3 | 3 | 9 | my($fn, $txt) = @_; | |||
322 | |||||||
323 | 3 | 100 | 12 | $txt //= ''; | |||
324 | 3 | 17 | my $out = _strip(qq( | ||||
325 | /** \@file $fn | ||||||
326 | $txt | ||||||
327 | */ | ||||||
328 | )); | ||||||
329 | |||||||
330 | 3 | 8 | return $out; | ||||
331 | } | ||||||
332 | |||||||
333 | |||||||
334 | sub _get_used_modules { | ||||||
335 | 3 | 3 | 8 | my($root) = @_; | |||
336 | |||||||
337 | 3 | 6 | my %used; | ||||
338 | 3 | 12 | for my $chld ( $root->schildren() ) { | ||||
339 | 52 | 100 | 489 | next unless $chld->isa('PPI::Statement::Include'); | |||
340 | 10 | 100 | 27 | next if $chld->pragma(); | |||
341 | 6 | 158 | $used{$chld->module()} = 1 | ||||
342 | } | ||||||
343 | 3 | 14 | return \%used; | ||||
344 | } | ||||||
345 | |||||||
346 | |||||||
347 | my %modifier = ( | ||||||
348 | has => 'Accessor Method', | ||||||
349 | before => 'Method Modifier: before', | ||||||
350 | after => 'Method Modifier: after', | ||||||
351 | around => 'Method Modifier: around', | ||||||
352 | fresh => 'Method Modifier: fresh', | ||||||
353 | ); | ||||||
354 | |||||||
355 | |||||||
356 | sub _parse_packages_subs { | ||||||
357 | 3 | 3 | 8 | my($self, $doc) = @_; | |||
358 | |||||||
359 | 3 | 6 | my %pkg_subs; | ||||
360 | |||||||
361 | my @main_pkgs = grep { | ||||||
362 | 3 | 22 | $_->isa('PPI::Statement::Package') | ||||
158 | 406 | ||||||
363 | } $doc->children(); | ||||||
364 | |||||||
365 | 3 | 100 | 17 | unless (@main_pkgs) { | |||
366 | 2 | 9 | $pkg_subs{'!main'}{used} = _get_used_modules($doc); | ||||
367 | 2 | 10 | my($v, $r) = $self->_get_pkg_version($doc); | ||||
368 | 2 | 5 | $pkg_subs{'!main'}{version} = $v; | ||||
369 | 2 | 6 | $pkg_subs{'!main'}{revision} = $r; | ||||
370 | } | ||||||
371 | |||||||
372 | 3 | 50 | 21 | my $stmt_nodes = $doc->find('PPI::Statement') || []; | |||
373 | 3 | 31675 | for my $stmt_node ( @$stmt_nodes ) { | ||||
374 | |||||||
375 | 148 | 934 | my $pkg = '!main'; | ||||
376 | 148 | 305 | my $mod = $stmt_node->child(0); | ||||
377 | next unless $stmt_node->class() eq 'PPI::Statement::Sub' | ||||||
378 | 148 | 100 | 100 | 808 | or $modifier{$mod}; | ||
379 | |||||||
380 | 19 | 112 | my $node = $stmt_node; | ||||
381 | 19 | 43 | while ($node) { | ||||
382 | 856 | 100 | 31209 | if ( $node->class() eq 'PPI::Statement::Package' ) { | |||
383 | 13 | 61 | $pkg = $node->namespace(); | ||||
384 | 13 | 50 | 294 | unless ( $pkg_subs{$pkg}{version} ) { | |||
385 | 13 | 26 | my($v, $r) = $self->_get_pkg_version($node->parent()); | ||||
386 | 13 | 23 | $pkg_subs{$pkg}{version} = $v; | ||||
387 | 13 | 18 | $pkg_subs{$pkg}{revision} = $r; | ||||
388 | } | ||||||
389 | 13 | 100 | 25 | unless ( defined $pkg_subs{$pkg}{inherit} ) { | |||
390 | my ($inherit) = _find_first_regex( | ||||||
391 | $node->parent(), | ||||||
392 | 'PPI::Statement::Include', | ||||||
393 | $self->{rx_parent}, | ||||||
394 | 1 | 4 | ); | ||||
395 | 1 | 3 | $pkg_subs{$pkg}{inherit} = $inherit; | ||||
396 | } | ||||||
397 | 13 | 100 | 27 | unless ( defined $pkg_subs{$pkg}{used} ) { | |||
398 | 1 | 4 | my $parent = $node->parent(); | ||||
399 | 1 | 50 | 10 | $pkg_subs{$pkg}{used} = _get_used_modules($parent) | |||
400 | if $parent; | ||||||
401 | } | ||||||
402 | } | ||||||
403 | 856 | 100 | 2824 | $node = $node->previous_sibling() || $node->parent(); | |||
404 | } | ||||||
405 | |||||||
406 | 19 | 100 | 159 | my $sub_name = $stmt_node->class() eq 'PPI::Statement::Sub' | |||
407 | ? $stmt_node->name | ||||||
408 | : $stmt_node->child(2)->content; | ||||||
409 | |||||||
410 | # split has sub_name => [qw(one two three)] | ||||||
411 | 19 | 100 | 473 | for my $sn ( grep { /\w/ && $_ ne 'qw' } split(/\W+/, $sub_name) ) { | |||
25 | 131 | ||||||
412 | 22 | 80 | $pkg_subs{$pkg}{subs}{$sn} = $stmt_node; | ||||
413 | 22 | 100 | 43 | $pkg_subs{$pkg}{mtype}{$sn} = $modifier{$mod} if $modifier{$mod}; | |||
414 | } | ||||||
415 | } | ||||||
416 | |||||||
417 | 3 | 35 | return \%pkg_subs; | ||||
418 | } | ||||||
419 | |||||||
420 | |||||||
421 | sub _out_process_subs { | ||||||
422 | 3 | 3 | 10 | my($class, $pkg_subs, $sub_info) = @_; | |||
423 | |||||||
424 | 3 | 7 | my $sub_nodes = $pkg_subs->{$class}{subs}; | ||||
425 | 3 | 8 | my $mod_types = $pkg_subs->{$class}{mtype}; | ||||
426 | |||||||
427 | 3 | 6 | my $out = ''; | ||||
428 | |||||||
429 | 3 | 6 | my %types; | ||||
430 | 3 | 14 | for my $sname ( sort keys %$sub_nodes ) { | ||||
431 | 23 | 100 | 96 | my $si = $sub_info->{$sname} || { | |||
432 | type => $sname =~ /^_/ ? 'private' : 'public', | ||||||
433 | rv => 'void', | ||||||
434 | params => [], | ||||||
435 | name => $sname, | ||||||
436 | static => 0, | ||||||
437 | virtual => 0, | ||||||
438 | class => $class, | ||||||
439 | text => ' Undocumented Function ', |
||||||
440 | }; | ||||||
441 | 23 | 51 | $types{ $si->{type} }{$sname} = $si; | ||||
442 | } | ||||||
443 | |||||||
444 | 3 | 8 | for my $type (qw/public private/) { | ||||
445 | 6 | 21 | $out .= "$type:\n"; | ||||
446 | 6 | 10 | for my $sname ( sort keys %{ $types{$type} } ) { | ||||
6 | 44 | ||||||
447 | 23 | 34 | my $si = $types{$type}{$sname}; | ||||
448 | 23 | 100 | 52 | my @static = $si->{static} ? 'static' : (); | |||
449 | 23 | 100 | 37 | my @virtual = $si->{virtual} ? 'virtual' : (); | |||
450 | |||||||
451 | 23 | 45 | my $fstr = join( ' ', @static, @virtual, $si->{rv}, "$sname(" ); | ||||
452 | 23 | 30 | $fstr .= join( ', ', @{ $si->{params} } ); | ||||
23 | 38 | ||||||
453 | 23 | 28 | $fstr .= ')'; | ||||
454 | |||||||
455 | 23 | 37 | $out .= "/** \@fn $fstr\n"; | ||||
456 | 23 | 100 | 52 | $out .= "$mod_types->{$sname}\n" if $mod_types->{$sname}; | |||
457 | 23 | 32 | $out .= $si->{text} . "\n"; | ||||
458 | 23 | 42 | $out .= _out_html_code( $sname, $sub_nodes->{$sname} ); | ||||
459 | 23 | 38 | $out .= "*/\n"; | ||||
460 | 23 | 45 | $out .= $fstr . ";\n\n"; | ||||
461 | } | ||||||
462 | } | ||||||
463 | |||||||
464 | 3 | 52 | return $out; | ||||
465 | } | ||||||
466 | |||||||
467 | |||||||
468 | sub _out_class_begin { | ||||||
469 | 3 | 3 | 17 | my($pname, $pkg_short, $namespace, $fname, $inherit, $used, $ver, $rev, $pod_txt) = @_; | |||
470 | |||||||
471 | 3 | 100 | 15 | if ( $pname eq '!main' ) { | |||
472 | 2 | 8 | $pkg_short = $pname = "${fname}_main"; | ||||
473 | } | ||||||
474 | |||||||
475 | 3 | 19 | my $out = ''; | ||||
476 | |||||||
477 | 3 | 100 | 11 | $out .= "namespace $namespace {\n" if $namespace; | |||
478 | |||||||
479 | 3 | 10 | $out .= "\n/** \@class $pname\n\n"; | ||||
480 | 3 | 50 | 9 | $out .= "\@version $ver" if $ver; | |||
481 | 3 | 50 | 11 | $out .= " rev:$rev" if $rev; | |||
482 | 3 | 6 | $out .= "\n\n"; | ||||
483 | |||||||
484 | 3 | 50 | 9 | if ($used) { | |||
485 | 3 | 8 | $out .= "\@section ${pkg_short}_USED_MODULES USED_MODULES\n"; | ||||
486 | 3 | 5 | $out .= "
|
||||
487 | 3 | 23 | for my $uname ( sort keys %$used ) { | ||||
488 | 6 | 26 | $out .= " |
||||
489 | } | ||||||
490 | 3 | 7 | $out .= "\n"; | ||||
491 | } | ||||||
492 | |||||||
493 | 3 | 8 | $out .= "$pod_txt\n*/\n\n"; | ||||
494 | |||||||
495 | 3 | 8 | $out .= "class $pkg_short: public"; | ||||
496 | 3 | 50 | 14 | $out .= " ::$inherit" if $inherit; | |||
497 | 3 | 7 | $out .= " {\n\n"; | ||||
498 | |||||||
499 | 3 | 8 | return $out; | ||||
500 | } | ||||||
501 | |||||||
502 | |||||||
503 | sub _out_class_end { | ||||||
504 | 3 | 3 | 9 | my($namespace) = @_; | |||
505 | |||||||
506 | 3 | 8 | my $out = "};\n"; | ||||
507 | 3 | 100 | 10 | $out .= "};\n" if $namespace; | |||
508 | |||||||
509 | 3 | 9 | return $out; | ||||
510 | } | ||||||
511 | |||||||
512 | |||||||
513 | sub _parse_pod { | ||||||
514 | 3 | 3 | 11 | my($self, $doc, $fname) = @_; | |||
515 | |||||||
516 | 3 | 43 | my $parser = Pod::POM->new(); | ||||
517 | |||||||
518 | 3 | 60 | my $txt = ''; | ||||
519 | 3 | 8 | my %subs; | ||||
520 | |||||||
521 | 3 | 14 | my $pod_tokens = $doc->find('PPI::Token::Pod'); | ||||
522 | |||||||
523 | 3 | 100 | 31405 | return '', \%subs unless $pod_tokens; | |||
524 | |||||||
525 | 2 | 2 | 27 | no warnings qw(once); | |||
2 | 4 | ||||||
2 | 3165 | ||||||
526 | 2 | 9 | $PPI::Transform::Doxygen::POD::PREFIX = $fname; | ||||
527 | 2 | 7 | for my $tok ( @$pod_tokens ) { | ||||
528 | 16 | 3894 | ( my $quoted = $tok->content() ) =~ s/(\@|\\|\%|#)/\\$1/g; | ||||
529 | 16 | 134 | my $pom = $parser->parse_text($quoted); | ||||
530 | 16 | 6670 | _filter_head2( $pom, \%subs ); | ||||
531 | 16 | 142 | $txt .= PPI::Transform::Doxygen::POD->print($pom); | ||||
532 | } | ||||||
533 | |||||||
534 | 2 | 575 | return $txt, \%subs; | ||||
535 | } | ||||||
536 | |||||||
537 | |||||||
538 | sub _filter_head2 { | ||||||
539 | 35 | 35 | 118 | my($pom, $sub_ref) = @_; | |||
540 | 35 | 41 | for my $sn ( @{ $pom->content() } ) { | ||||
35 | 132 | ||||||
541 | 27 | 100 | 100 | 407 | if ( $sn->type() eq 'head2' and $sn->title() =~ /[\w:]+\s*\(.*\)/ ) { | ||
542 | 8 | 580 | my $sinfo = _sub_extract( $sn->title() ); | ||||
543 | 8 | 50 | 34 | if ( $sinfo ) { | |||
544 | 8 | 50 | 36 | $sinfo->{text} = PPI::Transform::Doxygen::POD->print($sn) // ''; | |||
545 | 8 | 9700 | $sub_ref->{$sinfo->{name}} = $sinfo; | ||||
546 | 8 | 16 | $sn = ''; | ||||
547 | } | ||||||
548 | } | ||||||
549 | 27 | 100 | 314 | _filter_head2($sn, $sub_ref) if $sn; | |||
550 | } | ||||||
551 | } | ||||||
552 | |||||||
553 | |||||||
554 | my $rx_name_parms = qr/\s*([\w:]+)\s*\(\s*([^\)]*)\s*\)$/; | ||||||
555 | sub _sub_extract { | ||||||
556 | 8 | 8 | 117 | my($str) = @_; | |||
557 | |||||||
558 | |||||||
559 | 8 | 35 | my($long, $params) = $str =~ /$rx_name_parms/; | ||||
560 | 8 | 50 | 311 | return unless $long; | |||
561 | |||||||
562 | 8 | 26 | $str =~ s/$rx_name_parms//; | ||||
563 | |||||||
564 | 8 | 560 | my @parts = split(/\s+/, $str); | ||||
565 | |||||||
566 | 8 | 100 | 24 | my $rv = pop(@parts) || 'void'; | |||
567 | 8 | 18 | $rv =~ s/(\%|\@|\&)/\\$1/g; | ||||
568 | |||||||
569 | 8 | 100 | 27 | my $cat = pop(@parts) || ''; | |||
570 | |||||||
571 | 8 | 18 | my @params = _add_type($params); | ||||
572 | |||||||
573 | 8 | 20 | my @nparts = split( /::/, $long ); | ||||
574 | 8 | 22 | my $name = pop @nparts; | ||||
575 | 8 | 50 | 30 | my $class = join( '::', @nparts ) || '!main'; | |||
576 | |||||||
577 | 8 | 100 | 28 | my $static = $cat eq 'function' || $cat eq 'class_method'; | |||
578 | 8 | 100 | 23 | my $type = $name =~ /^_/ ? 'private' : 'public'; | |||
579 | |||||||
580 | return { | ||||||
581 | 8 | 50 | type => $type, | ||||
582 | rv => $rv, | ||||||
583 | params => \@params, | ||||||
584 | name => $name, | ||||||
585 | static => $static, | ||||||
586 | class => $class, | ||||||
587 | text => '', | ||||||
588 | }; | ||||||
589 | } | ||||||
590 | |||||||
591 | |||||||
592 | sub _add_type { | ||||||
593 | 15 | 100 | 15 | 40 | return unless my $params = shift; | ||
594 | |||||||
595 | 14 | 100 | 29 | unless ( ref($params) ) { | |||
596 | 7 | 14 | $params =~ s/\s//g; | ||||
597 | 7 | 22 | $params = [ split(/,/, $params) ]; | ||||
598 | } | ||||||
599 | |||||||
600 | return map { | ||||||
601 | 14 | 27 | my @sig = $_ =~ /^(.)(.)(.?)/; | ||||
25 | 88 | ||||||
602 | 25 | 100 | 57 | if ( $sig[0] eq '\\' ) { shift @sig } | |||
4 | 6 | ||||||
603 | 25 | 29 | my $ref; | ||||
604 | 25 | 100 | 50 | if ( $sig[1] eq '$' ) { $ref = 1; splice(@sig, 1, 1) } | |||
2 | 3 | ||||||
2 | 4 | ||||||
605 | 25 | 45 | my $typ = $vtype{ $sig[0] }; | ||||
606 | 25 | 100 | 50 | $typ .= '_ref' if $ref; | |||
607 | 25 | 57 | s/^\W*//; | ||||
608 | 25 | 104 | $_ = "$typ $_"; | ||||
609 | } @$params; | ||||||
610 | } | ||||||
611 | |||||||
612 | |||||||
613 | sub _find_first_regex { | ||||||
614 | 31 | 31 | 57 | my($root, $name, $regex) = @_; | |||
615 | 31 | 62 | for my $chld ( $root->schildren() ) { | ||||
616 | 479 | 100 | 4111 | next unless $chld->isa($name); | |||
617 | 15 | 50 | 43 | if ( my @capture = $chld->content() =~ /$regex/ ) { | |||
618 | 0 | 0 | return @capture; | ||||
619 | } | ||||||
620 | } | ||||||
621 | 31 | 59 | return ''; | ||||
622 | } | ||||||
623 | |||||||
624 | |||||||
625 | sub _get_pkg_version { | ||||||
626 | 15 | 15 | 62 | my($self, $root) = @_; | |||
627 | my($version) = _find_first_regex( | ||||||
628 | $root, | ||||||
629 | 'PPI::Statement::Variable', | ||||||
630 | $self->{rx_version}, | ||||||
631 | 15 | 37 | ); | ||||
632 | |||||||
633 | my($revision) = _find_first_regex( | ||||||
634 | $root, | ||||||
635 | 'PPI::Statement::Variable', | ||||||
636 | $self->{rx_revision}, | ||||||
637 | 15 | 26 | ); | ||||
638 | 15 | 33 | return $version, $revision; | ||||
639 | } | ||||||
640 | |||||||
641 | |||||||
642 | sub _out_html_code { | ||||||
643 | 23 | 23 | 37 | my($sname, $sub) = @_; | |||
644 | |||||||
645 | 23 | 66 | my $html = _strip(qq( | ||||
646 | \@htmlonly | ||||||
647 | |
||||||
648 | ![]() |
||||||
649 | |||||||
650 | click to view |
||||||
651 | |||||||
652 | \@endhtmlonly | ||||||
653 | \@code | ||||||
654 | )); | ||||||
655 | |||||||
656 | 23 | 60 | $html .= $sub; | ||||
657 | 23 | 1262 | $html .= "\n"; | ||||
658 | |||||||
659 | 23 | 40 | $html .= _strip(q( | ||||
660 | @endcode | ||||||
661 | @htmlonly | ||||||
662 | |||||||
663 | @endhtmlonly | ||||||
664 | )); | ||||||
665 | |||||||
666 | 23 | 107 | return $html; | ||||
667 | } | ||||||
668 | |||||||
669 | |||||||
670 | sub _sub_info_from_node { | ||||||
671 | 15 | 15 | 26 | my($sname, $class, $node) = @_; | |||
672 | |||||||
673 | 15 | 100 | 100 | 41 | return undef unless $node->class eq 'PPI::Statement::Sub' | ||
100 | |||||||
674 | or ($node->children > 6 and $node->child(6)->content eq 'sub'); | ||||||
675 | |||||||
676 | 9 | 140 | my $parser = Pod::POM->new(); | ||||
677 | 9 | 89 | my %si; | ||||
678 | 9 | 15 | my $txt = my $def = my $fmt = ''; | ||||
679 | 9 | 10 | my @params; | ||||
680 | 9 | 16 | my($rv, $static); | ||||
681 | 9 | 50 | 30 | my $type = $sname =~ /^_/ ? 'private' : 'public'; | |||
682 | |||||||
683 | 9 | 100 | 29 | my $pod = $node->find('PPI::Token::Pod') || []; | |||
684 | 9 | 5530 | for my $tok ( @$pod ) { | ||||
685 | 4 | 13 | ( my $quoted = $tok ) =~ s/(\@|\\|\%|#)/\\$1/g; | ||||
686 | 4 | 27 | my $pom = $parser->parse_text($quoted); | ||||
687 | 4 | 50 | 1314 | next unless my $for = $pom->for->[0]; | |||
688 | 4 | 78 | $rv = $for->text; | ||||
689 | 4 | 53 | $fmt = $for->format; | ||||
690 | 4 | 100 | 55 | $static = $fmt eq 'function' || $fmt eq 'class_method'; | |||
691 | 4 | 11 | $txt .= PPI::Transform::Doxygen::POD->print($pom); | ||||
692 | } | ||||||
693 | 9 | 100 | 1180 | my $proto = $node->find('PPI::Token::Prototype') || []; | |||
694 | 9 | 5354 | for my $tok ( @$proto ) { | ||||
695 | 7 | 21 | for my $pmt ( split(/,/, $tok->prototype) ) { | ||||
696 | 17 | 144 | my($attr, $default) = split(/=/, $pmt); | ||||
697 | 17 | 29 | push @params, $attr; | ||||
698 | 17 | 100 | 30 | next unless $default; | |||
699 | 3 | 10 | $def .= " Default value for $attr is $default. \n"; |
||||
700 | } | ||||||
701 | 7 | 17 | @params = _add_type(\@params); | ||||
702 | } | ||||||
703 | |||||||
704 | 9 | 100 | 35 | return undef unless $txt; | |||
705 | |||||||
706 | 4 | 100 | 10 | $txt .= "\n$def" if $def; | |||
707 | |||||||
708 | return { | ||||||
709 | 4 | 120 | type => $type, | ||||
710 | rv => $rv, | ||||||
711 | params => \@params, | ||||||
712 | name => $sname, | ||||||
713 | static => $static, | ||||||
714 | class => $class, | ||||||
715 | text => $txt, | ||||||
716 | regex => qr/\r?\n=for\s+$fmt\s+\Q$rv\E.+?\r?\n=cut\n\n?/s, | ||||||
717 | } | ||||||
718 | } | ||||||
719 | |||||||
720 | |||||||
721 | sub _integrate_sub_info { | ||||||
722 | 3 | 3 | 8 | my($pkg_subs, $sub_info) = @_; | |||
723 | |||||||
724 | 3 | 5 | my %look; | ||||
725 | 3 | 12 | for my $class ( keys %$pkg_subs ) { | ||||
726 | 3 | 6 | for my $subname ( keys %{ $pkg_subs->{$class}{subs} } ) { | ||||
3 | 19 | ||||||
727 | 22 | 100 | 43 | if ( $sub_info->{$subname} ) { | |||
728 | # pod info exists | ||||||
729 | 7 | 15 | $sub_info->{$subname}{class} = $class; | ||||
730 | 7 | 10 | $look{$subname} = 1; | ||||
731 | 7 | 10 | next; | ||||
732 | }; | ||||||
733 | my $si = _sub_info_from_node( | ||||||
734 | $subname, | ||||||
735 | $class, | ||||||
736 | 15 | 32 | $pkg_subs->{$class}{subs}{$subname}, | ||||
737 | ); | ||||||
738 | 15 | 100 | 89 | if ( $si ) { | |||
739 | 4 | 9 | $sub_info->{$subname} = $si; | ||||
740 | 4 | 20 | $pkg_subs->{$class}{subs}{$subname} =~ s/$si->{regex}//; | ||||
741 | 4 | 952 | $look{$subname} = 1; | ||||
742 | } | ||||||
743 | } | ||||||
744 | } | ||||||
745 | |||||||
746 | 3 | 11 | for my $si ( values %$sub_info ) { | ||||
747 | 12 | 100 | 29 | next if $look{ $si->{name} }; | |||
748 | 1 | 4 | $si->{virtual} = 1; | ||||
749 | $pkg_subs->{$si->{class}}{subs}{$si->{name}} | ||||||
750 | 1 | 4 | = ' virtual function or method '; |
||||
751 | } | ||||||
752 | } | ||||||
753 | |||||||
754 | 1; | ||||||
755 | |||||||
756 | =pod | ||||||
757 | |||||||
758 | =head1 AUTHOR | ||||||
759 | |||||||
760 | Thomas Kratz E |
||||||
761 | |||||||
762 | =head1 REPOSITORY | ||||||
763 | |||||||
764 | L |
||||||
765 | |||||||
766 | =head1 COPYRIGHT | ||||||
767 | |||||||
768 | Copyright 2016-2018 Thomas Kratz. | ||||||
769 | |||||||
770 | This program is free software; you can redistribute | ||||||
771 | it and/or modify it under the same terms as Perl itself. | ||||||
772 | |||||||
773 | =cut |