lib/PPI/Transform/Doxygen.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 288 | 294 | 97.9 |
branch | 92 | 112 | 82.1 |
condition | 49 | 66 | 74.2 |
subroutine | 31 | 31 | 100.0 |
pod | 3 | 4 | 75.0 |
total | 463 | 507 | 91.3 |
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 | 353214 | use strict; | |||
2 | 26 | ||||||
2 | 62 | ||||||
174 | 2 | 2 | 11 | use warnings; | |||
2 | 5 | ||||||
2 | 68 | ||||||
175 | |||||||
176 | 2 | 2 | 881 | use parent 'PPI::Transform'; | |||
2 | 580 | ||||||
2 | 13 | ||||||
177 | |||||||
178 | 2 | 2 | 2938 | use 5.010001; | |||
2 | 8 | ||||||
179 | 2 | 2 | 11 | use PPI; | |||
2 | 3 | ||||||
2 | 43 | ||||||
180 | 2 | 2 | 10 | use File::Basename qw(fileparse); | |||
2 | 5 | ||||||
2 | 137 | ||||||
181 | 2 | 2 | 1248 | use Pod::POM; | |||
2 | 40994 | ||||||
2 | 111 | ||||||
182 | 2 | 2 | 979 | use Pod::POM::View::Text; | |||
2 | 9953 | ||||||
2 | 80 | ||||||
183 | 2 | 2 | 839 | use PPI::Transform::Doxygen::POD; | |||
2 | 5 | ||||||
2 | 69 | ||||||
184 | 2 | 2 | 15 | use Params::Util qw{_INSTANCE}; | |||
2 | 4 | ||||||
2 | 4649 | ||||||
185 | |||||||
186 | our $VERSION = '0.34'; | ||||||
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 | 294 | my ( $class, %args ) = @_; | ||
220 | |||||||
221 | 2 | 25 | my $self = shift->SUPER::new(%defaults); | ||||
222 | |||||||
223 | 2 | 27 | @$self{ keys %args } = values %args; | ||||
224 | |||||||
225 | 2 | 7 | 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 | 3562 | my ($self, $in, $out) = @_; | ||
241 | |||||||
242 | 3 | 50 | 13 | return unless $in; | |||
243 | |||||||
244 | 3 | 33 | 14 | my $preserve = !$out && !$self->{overwrite}; | |||
245 | |||||||
246 | 3 | 50 | 25 | my $Document = PPI::Document->new($in) or return undef; | |||
247 | 3 | 184321 | $Document->{_in_fn} = $in; | ||||
248 | 3 | 50 | 20 | $self->document($Document, $preserve) or return undef; | |||
249 | |||||||
250 | 3 | 33 | 223 | $out //= $in; | |||
251 | |||||||
252 | 3 | 50 | 15 | if ( ref($out) eq 'GLOB' ) { | |||
253 | 3 | 22 | 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 | 11 | my ( $self, $doc, $preserve ) = @_; | ||
271 | |||||||
272 | 3 | 50 | 27 | _INSTANCE( $doc, 'PPI::Document' ) or return undef; | |||
273 | |||||||
274 | 3 | 21 | my $pkg_subs = $self->_parse_packages_subs($doc); | ||||
275 | |||||||
276 | 3 | 250 | my($fname, $fdir, $fext) = fileparse( $doc->{_in_fn}, qr/\..*/ ); | ||||
277 | |||||||
278 | 3 | 30 | my($pod_txt, $sub_info) = $self->_parse_pod($doc, $fname); | ||||
279 | |||||||
280 | 3 | 18 | _integrate_sub_info($pkg_subs, $sub_info); | ||||
281 | |||||||
282 | 3 | 18 | my @packages = sort keys %$pkg_subs; | ||||
283 | 3 | 100 | 66 | 24 | my $file_pod = $pod_txt if @packages == 1 and $packages[0] eq '!main'; | ||
284 | |||||||
285 | 3 | 19 | my $dxout = _out_head($fname . $fext, $file_pod); | ||||
286 | |||||||
287 | 3 | 10 | for my $pname ( @packages ) { | ||||
288 | |||||||
289 | 3 | 15 | my @parts = split( /::/, $pname ); | ||||
290 | 3 | 9 | my $short = pop @parts; | ||||
291 | 3 | 100 | 27 | 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 | 37 | $short eq $fname ? $pod_txt : '', | |||
300 | ); | ||||||
301 | |||||||
302 | 3 | 15 | $dxout .= _out_process_subs( $pname, $pkg_subs, $sub_info ); | ||||
303 | |||||||
304 | 3 | 17 | $dxout .= _out_class_end($namespace); | ||||
305 | } | ||||||
306 | |||||||
307 | 3 | 50 | 15 | unless ($preserve) { | |||
308 | 3 | 19 | $_->delete for $doc->children(); | ||||
309 | } | ||||||
310 | |||||||
311 | 3 | 33 | 14300 | my $end_tok = $doc->find_first('PPI::Token::End') || PPI::Token::End->new(); | |||
312 | 3 | 1484 | $end_tok->add_content($dxout); | ||||
313 | 3 | 75 | $doc->add_element($end_tok); | ||||
314 | } | ||||||
315 | |||||||
316 | |||||||
317 | 49 | 49 | 74 | sub _strip { my $str = shift; $str =~ s/^ +//mg; $str } | |||
49 | 321 | ||||||
49 | 117 | ||||||
318 | |||||||
319 | |||||||
320 | sub _out_head { | ||||||
321 | 3 | 3 | 10 | my($fn, $txt) = @_; | |||
322 | |||||||
323 | 3 | 100 | 16 | $txt //= ''; | |||
324 | 3 | 19 | my $out = _strip(qq( | ||||
325 | /** \@file $fn | ||||||
326 | $txt | ||||||
327 | */ | ||||||
328 | )); | ||||||
329 | |||||||
330 | 3 | 10 | return $out; | ||||
331 | } | ||||||
332 | |||||||
333 | |||||||
334 | sub _get_used_modules { | ||||||
335 | 3 | 3 | 10 | my($root) = @_; | |||
336 | |||||||
337 | 3 | 6 | my %used; | ||||
338 | 3 | 16 | for my $chld ( $root->schildren() ) { | ||||
339 | 53 | 100 | 33 | 4839 | if ( $chld->isa('PPI::Statement::Include') ) { | ||
50 | |||||||
340 | 10 | 100 | 33 | next if $chld->pragma(); | |||
341 | 6 | 193 | $used{$chld->module()} = 1 | ||||
342 | } elsif ( $chld->isa('PPI::Statement') and $chld->content =~ /^\s*with/ ) { | ||||||
343 | 0 | 0 | my @tokens = $chld->children; | ||||
344 | 0 | 0 | for my $tok ( @tokens ) { | ||||
345 | 0 | 0 | 0 | 0 | if ( $tok->isa('PPI::Token::Quote') or $tok->isa('PPI::Token::QuoteLike::Words') ) { | ||
346 | 0 | 0 | $used{$_} = 1 for $tok->literal; | ||||
347 | } | ||||||
348 | } | ||||||
349 | } | ||||||
350 | } | ||||||
351 | 3 | 759 | return \%used; | ||||
352 | } | ||||||
353 | |||||||
354 | |||||||
355 | my %modifier = ( | ||||||
356 | has => 'Accessor Method', | ||||||
357 | before => 'Method Modifier: before', | ||||||
358 | after => 'Method Modifier: after', | ||||||
359 | around => 'Method Modifier: around', | ||||||
360 | fresh => 'Method Modifier: fresh', | ||||||
361 | ); | ||||||
362 | |||||||
363 | |||||||
364 | sub _parse_packages_subs { | ||||||
365 | 3 | 3 | 8 | my($self, $doc) = @_; | |||
366 | |||||||
367 | 3 | 7 | my %pkg_subs; | ||||
368 | |||||||
369 | my @main_pkgs = grep { | ||||||
370 | 3 | 21 | $_->isa('PPI::Statement::Package') | ||||
158 | 435 | ||||||
371 | } $doc->children(); | ||||||
372 | |||||||
373 | 3 | 100 | 17 | unless (@main_pkgs) { | |||
374 | 2 | 10 | $pkg_subs{'!main'}{used} = _get_used_modules($doc); | ||||
375 | 2 | 11 | my($v, $r) = $self->_get_pkg_version($doc); | ||||
376 | 2 | 8 | $pkg_subs{'!main'}{version} = $v; | ||||
377 | 2 | 7 | $pkg_subs{'!main'}{revision} = $r; | ||||
378 | } | ||||||
379 | |||||||
380 | 3 | 50 | 17 | my $stmt_nodes = $doc->find('PPI::Statement') || []; | |||
381 | 3 | 38217 | for my $stmt_node ( @$stmt_nodes ) { | ||||
382 | |||||||
383 | 149 | 1156 | my $pkg = '!main'; | ||||
384 | 149 | 379 | my $mod = $stmt_node->child(0); | ||||
385 | next unless $stmt_node->class() eq 'PPI::Statement::Sub' | ||||||
386 | 149 | 100 | 100 | 999 | or $modifier{$mod}; | ||
387 | |||||||
388 | 19 | 102 | my $node = $stmt_node; | ||||
389 | 19 | 59 | while ($node) { | ||||
390 | 836 | 100 | 40099 | if ( $node->class() eq 'PPI::Statement::Package' ) { | |||
391 | 13 | 67 | $pkg = $node->namespace(); | ||||
392 | 13 | 50 | 322 | unless ( $pkg_subs{$pkg}{version} ) { | |||
393 | 13 | 38 | my($v, $r) = $self->_get_pkg_version($node->parent()); | ||||
394 | 13 | 29 | $pkg_subs{$pkg}{version} = $v; | ||||
395 | 13 | 25 | $pkg_subs{$pkg}{revision} = $r; | ||||
396 | } | ||||||
397 | 13 | 100 | 28 | unless ( defined $pkg_subs{$pkg}{inherit} ) { | |||
398 | my ($inherit) = _find_first_regex( | ||||||
399 | $node->parent(), | ||||||
400 | 'PPI::Statement::Include', | ||||||
401 | $self->{rx_parent}, | ||||||
402 | 1 | 4 | ); | ||||
403 | 1 | 21 | $pkg_subs{$pkg}{inherit} = $inherit; | ||||
404 | } | ||||||
405 | 13 | 100 | 32 | unless ( defined $pkg_subs{$pkg}{used} ) { | |||
406 | 1 | 5 | my $parent = $node->parent(); | ||||
407 | 1 | 50 | 14 | $pkg_subs{$pkg}{used} = _get_used_modules($parent) | |||
408 | if $parent; | ||||||
409 | } | ||||||
410 | } | ||||||
411 | 836 | 100 | 3398 | $node = $node->previous_sibling() || $node->parent(); | |||
412 | } | ||||||
413 | |||||||
414 | 19 | 100 | 200 | my $sub_name = $stmt_node->class() eq 'PPI::Statement::Sub' | |||
415 | ? $stmt_node->name | ||||||
416 | : $stmt_node->child(2)->content; | ||||||
417 | |||||||
418 | # split has sub_name => [qw(one two three)] | ||||||
419 | 19 | 100 | 526 | for my $sn ( grep { /\w/ && $_ ne 'qw' } split(/\W+/, $sub_name) ) { | |||
25 | 156 | ||||||
420 | 22 | 85 | $pkg_subs{$pkg}{subs}{$sn} = $stmt_node; | ||||
421 | 22 | 100 | 51 | $pkg_subs{$pkg}{mtype}{$sn} = $modifier{$mod} if $modifier{$mod}; | |||
422 | } | ||||||
423 | } | ||||||
424 | |||||||
425 | 3 | 44 | return \%pkg_subs; | ||||
426 | } | ||||||
427 | |||||||
428 | |||||||
429 | sub _out_process_subs { | ||||||
430 | 3 | 3 | 14 | my($class, $pkg_subs, $sub_info) = @_; | |||
431 | |||||||
432 | 3 | 15 | my $sub_nodes = $pkg_subs->{$class}{subs}; | ||||
433 | 3 | 20 | my $mod_types = $pkg_subs->{$class}{mtype}; | ||||
434 | |||||||
435 | 3 | 11 | my $out = ''; | ||||
436 | |||||||
437 | 3 | 7 | my %types; | ||||
438 | 3 | 22 | for my $sname ( sort keys %$sub_nodes ) { | ||||
439 | 23 | 100 | 121 | my $si = $sub_info->{$sname} || { | |||
440 | type => $sname =~ /^_/ ? 'private' : 'public', | ||||||
441 | rv => 'void', | ||||||
442 | params => [], | ||||||
443 | name => $sname, | ||||||
444 | static => 0, | ||||||
445 | virtual => 0, | ||||||
446 | class => $class, | ||||||
447 | text => ' Undocumented Function ', |
||||||
448 | }; | ||||||
449 | 23 | 60 | $types{ $si->{type} }{$sname} = $si; | ||||
450 | } | ||||||
451 | |||||||
452 | 3 | 9 | for my $type (qw/public private/) { | ||||
453 | 6 | 14 | $out .= "$type:\n"; | ||||
454 | 6 | 13 | for my $sname ( sort keys %{ $types{$type} } ) { | ||||
6 | 34 | ||||||
455 | 23 | 45 | my $si = $types{$type}{$sname}; | ||||
456 | 23 | 100 | 79 | my @static = $si->{static} ? 'static' : (); | |||
457 | 23 | 100 | 56 | my @virtual = $si->{virtual} ? 'virtual' : (); | |||
458 | |||||||
459 | 23 | 54 | my $fstr = join( ' ', @static, @virtual, $si->{rv}, "$sname(" ); | ||||
460 | 23 | 41 | $fstr .= join( ', ', @{ $si->{params} } ); | ||||
23 | 47 | ||||||
461 | 23 | 38 | $fstr .= ')'; | ||||
462 | |||||||
463 | 23 | 49 | $out .= "/** \@fn $fstr\n"; | ||||
464 | 23 | 100 | 60 | $out .= "$mod_types->{$sname}\n" if $mod_types->{$sname}; | |||
465 | 23 | 40 | $out .= $si->{text} . "\n"; | ||||
466 | 23 | 51 | $out .= _out_html_code( $sname, $sub_nodes->{$sname} ); | ||||
467 | 23 | 43 | $out .= "*/\n"; | ||||
468 | 23 | 75 | $out .= $fstr . ";\n\n"; | ||||
469 | } | ||||||
470 | } | ||||||
471 | |||||||
472 | 3 | 74 | return $out; | ||||
473 | } | ||||||
474 | |||||||
475 | |||||||
476 | sub _out_class_begin { | ||||||
477 | 3 | 3 | 19 | my($pname, $pkg_short, $namespace, $fname, $inherit, $used, $ver, $rev, $pod_txt) = @_; | |||
478 | |||||||
479 | 3 | 100 | 13 | if ( $pname eq '!main' ) { | |||
480 | 2 | 8 | $pkg_short = $pname = "${fname}_main"; | ||||
481 | } | ||||||
482 | |||||||
483 | 3 | 6 | my $out = ''; | ||||
484 | |||||||
485 | 3 | 100 | 11 | $out .= "namespace $namespace {\n" if $namespace; | |||
486 | |||||||
487 | 3 | 13 | $out .= "\n/** \@class $pname\n\n"; | ||||
488 | 3 | 50 | 25 | $out .= "\@version $ver" if $ver; | |||
489 | 3 | 50 | 9 | $out .= " rev:$rev" if $rev; | |||
490 | 3 | 10 | $out .= "\n\n"; | ||||
491 | |||||||
492 | 3 | 50 | 10 | if ($used) { | |||
493 | 3 | 11 | $out .= "\@section ${pkg_short}_USED_MODULES USED_MODULES\n"; | ||||
494 | 3 | 12 | $out .= "
|
||||
495 | 3 | 22 | for my $uname ( sort keys %$used ) { | ||||
496 | 6 | 20 | $out .= " |
||||
497 | } | ||||||
498 | 3 | 8 | $out .= "\n"; | ||||
499 | } | ||||||
500 | |||||||
501 | 3 | 9 | $out .= "$pod_txt\n*/\n\n"; | ||||
502 | |||||||
503 | 3 | 9 | $out .= "class $pkg_short: public"; | ||||
504 | 3 | 50 | 11 | $out .= " ::$inherit" if $inherit; | |||
505 | 3 | 6 | $out .= " {\n\n"; | ||||
506 | |||||||
507 | 3 | 10 | return $out; | ||||
508 | } | ||||||
509 | |||||||
510 | |||||||
511 | sub _out_class_end { | ||||||
512 | 3 | 3 | 8 | my($namespace) = @_; | |||
513 | |||||||
514 | 3 | 7 | my $out = "};\n"; | ||||
515 | 3 | 100 | 10 | $out .= "};\n" if $namespace; | |||
516 | |||||||
517 | 3 | 18 | return $out; | ||||
518 | } | ||||||
519 | |||||||
520 | |||||||
521 | sub get_pom { | ||||||
522 | 16 | 16 | 0 | 81 | my($txt) = @_; | ||
523 | 16 | 91 | ( my $quoted = $txt ) =~ s/(\@|\\|\%|#)/\\$1/g; | ||||
524 | 16 | 79 | my $parser = Pod::POM->new(); | ||||
525 | 16 | 222 | my $pom = $parser->parse_text($quoted); | ||||
526 | 16 | 8057 | return $pom; | ||||
527 | } | ||||||
528 | |||||||
529 | |||||||
530 | sub _parse_pod { | ||||||
531 | 3 | 3 | 13 | my($self, $doc, $fname) = @_; | |||
532 | |||||||
533 | 3 | 8 | my $txt = ''; | ||||
534 | 3 | 12 | $PPI::Transform::Doxygen::POD::PREFIX = $fname; | ||||
535 | |||||||
536 | 3 | 100 | 18 | my $parts = $doc->find('PPI::Statement::Data') || []; | |||
537 | 3 | 38569 | my $ptxt = join('', @$parts); | ||||
538 | 3 | 100 | 44 | $txt .= PPI::Transform::Doxygen::POD->print(get_pom($ptxt)) | |||
539 | if $ptxt =~ /\w/; | ||||||
540 | |||||||
541 | 3 | 163 | my %subs; | ||||
542 | 3 | 393 | my $pod_tokens = $doc->find('PPI::Token::Pod'); | ||||
543 | 3 | 100 | 37999 | return '', \%subs unless $pod_tokens; | |||
544 | |||||||
545 | 2 | 2 | 17 | no warnings qw(once); | |||
2 | 5 | ||||||
2 | 3716 | ||||||
546 | 2 | 11 | for my $tok ( @$pod_tokens ) { | ||||
547 | 15 | 3604 | my $pom = get_pom($tok->content); | ||||
548 | 15 | 57 | _filter_head2( $pom, \%subs ); | ||||
549 | 15 | 159 | my $for = $pom->for->[0]; | ||||
550 | 15 | 100 | 66 | 242 | next if $for and $for->format =~ /(?:function|method|class_method)/; | ||
551 | 10 | 33 | $txt .= PPI::Transform::Doxygen::POD->print($pom); | ||||
552 | } | ||||||
553 | |||||||
554 | 2 | 581 | return $txt, \%subs; | ||||
555 | } | ||||||
556 | |||||||
557 | |||||||
558 | sub _filter_head2 { | ||||||
559 | 30 | 30 | 117 | my($pom, $sub_ref) = @_; | |||
560 | 30 | 49 | for my $sn ( @{ $pom->content() } ) { | ||||
30 | 140 | ||||||
561 | 23 | 100 | 100 | 458 | if ( $sn->type() eq 'head2' and $sn->title() =~ /[\w:]+\s*\(.*\)/ ) { | ||
562 | 8 | 709 | my $sinfo = _sub_extract( $sn->title() ); | ||||
563 | 8 | 50 | 24 | if ( $sinfo ) { | |||
564 | 8 | 50 | 45 | $sinfo->{text} = PPI::Transform::Doxygen::POD->print($sn) // ''; | |||
565 | 8 | 12569 | $sub_ref->{$sinfo->{name}} = $sinfo; | ||||
566 | 8 | 20 | $sn = ''; | ||||
567 | } | ||||||
568 | } | ||||||
569 | 23 | 100 | 322 | _filter_head2($sn, $sub_ref) if $sn; | |||
570 | } | ||||||
571 | } | ||||||
572 | |||||||
573 | |||||||
574 | my $rx_name_parms = qr/\s*([\w:]+)\s*\(\s*([^\)]*)\s*\)$/; | ||||||
575 | sub _sub_extract { | ||||||
576 | 8 | 8 | 145 | my($str) = @_; | |||
577 | |||||||
578 | |||||||
579 | 8 | 43 | my($long, $params) = $str =~ /$rx_name_parms/; | ||||
580 | 8 | 50 | 442 | return unless $long; | |||
581 | |||||||
582 | 8 | 40 | $str =~ s/$rx_name_parms//; | ||||
583 | |||||||
584 | 8 | 708 | my @parts = split(/\s+/, $str); | ||||
585 | |||||||
586 | 8 | 100 | 31 | my $rv = pop(@parts) || 'void'; | |||
587 | 8 | 30 | $rv =~ s/(\%|\@|\&)/\\$1/g; | ||||
588 | |||||||
589 | 8 | 100 | 32 | my $cat = pop(@parts) || ''; | |||
590 | |||||||
591 | 8 | 34 | my @params = _add_type($params); | ||||
592 | |||||||
593 | 8 | 31 | my @nparts = split( /::/, $long ); | ||||
594 | 8 | 14 | my $name = pop @nparts; | ||||
595 | 8 | 50 | 38 | my $class = join( '::', @nparts ) || '!main'; | |||
596 | |||||||
597 | 8 | 100 | 41 | my $static = $cat eq 'function' || $cat eq 'class_method'; | |||
598 | 8 | 100 | 30 | my $type = $name =~ /^_/ ? 'private' : 'public'; | |||
599 | |||||||
600 | return { | ||||||
601 | 8 | 56 | type => $type, | ||||
602 | rv => $rv, | ||||||
603 | params => \@params, | ||||||
604 | name => $name, | ||||||
605 | static => $static, | ||||||
606 | class => $class, | ||||||
607 | text => '', | ||||||
608 | }; | ||||||
609 | } | ||||||
610 | |||||||
611 | |||||||
612 | sub _add_type { | ||||||
613 | 15 | 100 | 15 | 42 | return unless my $params = shift; | ||
614 | |||||||
615 | 14 | 100 | 38 | unless ( ref($params) ) { | |||
616 | 7 | 18 | $params =~ s/\s//g; | ||||
617 | 7 | 26 | $params = [ split(/,/, $params) ]; | ||||
618 | } | ||||||
619 | |||||||
620 | return map { | ||||||
621 | 14 | 32 | my @sig = $_ =~ /^(.)(.)(.?)/; | ||||
25 | 113 | ||||||
622 | 25 | 100 | 63 | if ( $sig[0] eq '\\' ) { shift @sig } | |||
4 | 8 | ||||||
623 | 25 | 46 | my $ref; | ||||
624 | 25 | 100 | 51 | if ( $sig[1] eq '$' ) { $ref = 1; splice(@sig, 1, 1) } | |||
2 | 5 | ||||||
2 | 6 | ||||||
625 | 25 | 50 | 69 | my $typ = $vtype{ $sig[0] } || ''; | |||
626 | 25 | 100 | 56 | $typ .= '_ref' if $ref; | |||
627 | 25 | 72 | s/^\W*//; | ||||
628 | 25 | 104 | $_ = "$typ $_"; | ||||
629 | } @$params; | ||||||
630 | } | ||||||
631 | |||||||
632 | |||||||
633 | sub _find_first_regex { | ||||||
634 | 31 | 31 | 64 | my($root, $name, $regex) = @_; | |||
635 | 31 | 68 | for my $chld ( $root->schildren() ) { | ||||
636 | 506 | 100 | 4806 | next unless $chld->isa($name); | |||
637 | 15 | 50 | 31 | if ( my @capture = $chld->content() =~ /$regex/ ) { | |||
638 | 0 | 0 | return @capture; | ||||
639 | } | ||||||
640 | } | ||||||
641 | 31 | 78 | return ''; | ||||
642 | } | ||||||
643 | |||||||
644 | |||||||
645 | sub _get_pkg_version { | ||||||
646 | 15 | 15 | 73 | my($self, $root) = @_; | |||
647 | my($version) = _find_first_regex( | ||||||
648 | $root, | ||||||
649 | 'PPI::Statement::Variable', | ||||||
650 | $self->{rx_version}, | ||||||
651 | 15 | 46 | ); | ||||
652 | |||||||
653 | my($revision) = _find_first_regex( | ||||||
654 | $root, | ||||||
655 | 'PPI::Statement::Variable', | ||||||
656 | $self->{rx_revision}, | ||||||
657 | 15 | 39 | ); | ||||
658 | 15 | 38 | return $version, $revision; | ||||
659 | } | ||||||
660 | |||||||
661 | |||||||
662 | sub _out_html_code { | ||||||
663 | 23 | 23 | 44 | my($sname, $sub) = @_; | |||
664 | |||||||
665 | 23 | 71 | my $html = _strip(qq( | ||||
666 | \@htmlonly | ||||||
667 | |
||||||
668 | ![]() |
||||||
669 | |||||||
670 | click to view |
||||||
671 | |||||||
672 | \@endhtmlonly | ||||||
673 | \@code | ||||||
674 | )); | ||||||
675 | |||||||
676 | 23 | 74 | $html .= $sub; | ||||
677 | 23 | 1556 | $html .= "\n"; | ||||
678 | |||||||
679 | 23 | 44 | $html .= _strip(q( | ||||
680 | @endcode | ||||||
681 | @htmlonly | ||||||
682 | |||||||
683 | @endhtmlonly | ||||||
684 | )); | ||||||
685 | |||||||
686 | 23 | 100 | return $html; | ||||
687 | } | ||||||
688 | |||||||
689 | |||||||
690 | sub _sub_info_from_node { | ||||||
691 | 15 | 15 | 32 | my($sname, $class, $node) = @_; | |||
692 | |||||||
693 | 15 | 100 | 100 | 41 | return undef unless $node->class eq 'PPI::Statement::Sub' | ||
100 | |||||||
694 | or ($node->children > 6 and $node->child(6)->content eq 'sub'); | ||||||
695 | |||||||
696 | 9 | 173 | my $parser = Pod::POM->new(); | ||||
697 | 9 | 96 | my %si; | ||||
698 | 9 | 19 | my $txt = my $def = my $fmt = ''; | ||||
699 | 9 | 15 | my @params; | ||||
700 | 9 | 13 | my($rv, $static); | ||||
701 | 9 | 50 | 26 | my $type = $sname =~ /^_/ ? 'private' : 'public'; | |||
702 | |||||||
703 | 9 | 100 | 29 | my $pod = $node->find('PPI::Token::Pod') || []; | |||
704 | 9 | 6552 | for my $tok ( @$pod ) { | ||||
705 | 4 | 16 | ( my $quoted = $tok ) =~ s/(\@|\\|\%|#)/\\$1/g; | ||||
706 | 4 | 31 | my $pom = $parser->parse_text($quoted); | ||||
707 | 4 | 50 | 1593 | next unless my $for = $pom->for->[0]; | |||
708 | 4 | 141 | $rv = $for->text; | ||||
709 | 4 | 73 | $fmt = $for->format; | ||||
710 | 4 | 100 | 69 | $static = $fmt eq 'function' || $fmt eq 'class_method'; | |||
711 | 4 | 16 | $txt .= PPI::Transform::Doxygen::POD->print($pom); | ||||
712 | } | ||||||
713 | 9 | 100 | 1388 | my $proto = $node->find('PPI::Token::Prototype') || []; | |||
714 | 9 | 6425 | for my $tok ( @$proto ) { | ||||
715 | 7 | 31 | for my $pmt ( split(/,/, $tok->prototype) ) { | ||||
716 | 17 | 175 | my($attr, $default) = split(/=/, $pmt); | ||||
717 | 17 | 35 | push @params, $attr; | ||||
718 | 17 | 100 | 40 | next unless $default; | |||
719 | 3 | 10 | $def .= " Default value for $attr is $default. \n"; |
||||
720 | } | ||||||
721 | 7 | 21 | @params = _add_type(\@params); | ||||
722 | } | ||||||
723 | |||||||
724 | 9 | 100 | 41 | return undef unless $txt; | |||
725 | |||||||
726 | 4 | 100 | 12 | $txt .= "\n$def" if $def; | |||
727 | |||||||
728 | return { | ||||||
729 | 4 | 111 | type => $type, | ||||
730 | rv => $rv, | ||||||
731 | params => \@params, | ||||||
732 | name => $sname, | ||||||
733 | static => $static, | ||||||
734 | class => $class, | ||||||
735 | text => $txt, | ||||||
736 | regex => qr/\r?\n=for\s+$fmt\s+\Q$rv\E.+?\r?\n=cut\n\n?/s, | ||||||
737 | } | ||||||
738 | } | ||||||
739 | |||||||
740 | |||||||
741 | sub _integrate_sub_info { | ||||||
742 | 3 | 3 | 11 | my($pkg_subs, $sub_info) = @_; | |||
743 | |||||||
744 | 3 | 7 | my %look; | ||||
745 | 3 | 14 | for my $class ( keys %$pkg_subs ) { | ||||
746 | 3 | 7 | for my $subname ( keys %{ $pkg_subs->{$class}{subs} } ) { | ||||
3 | 21 | ||||||
747 | 22 | 100 | 56 | if ( $sub_info->{$subname} ) { | |||
748 | # pod info exists | ||||||
749 | 7 | 15 | $sub_info->{$subname}{class} = $class; | ||||
750 | 7 | 15 | $look{$subname} = 1; | ||||
751 | 7 | 11 | next; | ||||
752 | }; | ||||||
753 | my $si = _sub_info_from_node( | ||||||
754 | $subname, | ||||||
755 | $class, | ||||||
756 | 15 | 40 | $pkg_subs->{$class}{subs}{$subname}, | ||||
757 | ); | ||||||
758 | 15 | 100 | 119 | if ( $si ) { | |||
759 | 4 | 12 | $sub_info->{$subname} = $si; | ||||
760 | 4 | 23 | $pkg_subs->{$class}{subs}{$subname} =~ s/$si->{regex}//; | ||||
761 | 4 | 1105 | $look{$subname} = 1; | ||||
762 | } | ||||||
763 | } | ||||||
764 | } | ||||||
765 | |||||||
766 | 3 | 14 | for my $si ( values %$sub_info ) { | ||||
767 | 12 | 100 | 35 | next if $look{ $si->{name} }; | |||
768 | 1 | 4 | $si->{virtual} = 1; | ||||
769 | $pkg_subs->{$si->{class}}{subs}{$si->{name}} | ||||||
770 | 1 | 6 | = ' virtual function or method '; |
||||
771 | } | ||||||
772 | } | ||||||
773 | |||||||
774 | 1; | ||||||
775 | |||||||
776 | =pod | ||||||
777 | |||||||
778 | =head1 AUTHOR | ||||||
779 | |||||||
780 | Thomas Kratz E |
||||||
781 | |||||||
782 | =head1 REPOSITORY | ||||||
783 | |||||||
784 | L |
||||||
785 | |||||||
786 | =head1 COPYRIGHT | ||||||
787 | |||||||
788 | Copyright 2016-2018 Thomas Kratz. | ||||||
789 | |||||||
790 | This program is free software; you can redistribute | ||||||
791 | it and/or modify it under the same terms as Perl itself. | ||||||
792 | |||||||
793 | =cut |