File Coverage

blib/lib/Test/Auto/Document.pm
Criterion Covered Total %
statement 196 254 77.1
branch 27 68 39.7
condition 14 36 38.8
subroutine 33 35 94.2
pod 1 29 3.4
total 271 422 64.2


line stmt bran cond sub pod time code
1             package Test::Auto::Document;
2              
3 2     2   780 use strict;
  2         4  
  2         68  
4 2     2   18 use warnings;
  2         4  
  2         88  
5              
6 2     2   13 use Moo;
  2         3  
  2         15  
7 2     2   817 use Test::Auto::Types ();
  2         4  
  2         44  
8 2     2   10 use Test::More;
  2         4  
  2         11  
9 2     2   528 use Type::Registry;
  2         4  
  2         18  
10              
11             require Carp;
12              
13             our $VERSION = '0.12'; # VERSION
14              
15             # ATTRIBUTES
16              
17             has content => (
18             is => 'ro',
19             isa => Test::Auto::Types::Strings()
20             );
21              
22             has template => (
23             is => 'ro',
24             isa => Test::Auto::Types::Maybe([Test::Auto::Types::Str()]),
25             default => $ENV{TEST_AUTO_TEMPLATE}
26             );
27              
28             has parser => (
29             is => 'ro',
30             isa => Test::Auto::Types::Parser(),
31             required => 1
32             );
33              
34             # BUILD
35              
36             sub BUILD {
37 6     6 0 5632 my ($self, $args) = @_;
38              
39             # build content from parser data
40 6 50       25 $self->{content} = $self->construct if !$args->{content};
41              
42 6         146 return $self;
43             }
44              
45             # METHODS
46              
47             sub construct {
48 6     6 0 15 my ($self) = @_;
49              
50 6         13 my $content = [];
51              
52 6         21 push @$content, $self->construct_name;
53 6         16 push @$content, $self->construct_abstract;
54 6         19 push @$content, $self->construct_synopsis;
55 6         16 push @$content, $self->construct_description;
56 6         15 push @$content, $self->construct_headers;
57 6         19 push @$content, $self->construct_inherits;
58 6         16 push @$content, $self->construct_integrates;
59 6         22 push @$content, $self->construct_libraries;
60 6         17 push @$content, $self->construct_constraints;
61 6         35 push @$content, $self->construct_scenarios;
62 6         18 push @$content, $self->construct_attributes;
63 6         18 push @$content, $self->construct_functions;
64 6         17 push @$content, $self->construct_routines;
65 6         21 push @$content, $self->construct_methods;
66 6         18 push @$content, $self->construct_footers;
67              
68 6         22 return $content;
69             }
70              
71             sub construct_name {
72 6     6 0 10 my ($self) = @_;
73              
74 6         15 my $parser = $self->parser;
75 6         14 my $name = $parser->name;
76              
77 6 50       19 if (my $tagline = $parser->tagline) {
78 6 50       27 $name->[0] = $name->[0] .' - '. $tagline->[0] if @$tagline;
79             }
80              
81 6         16 return $self->head1('name', $name);
82             }
83              
84             sub construct_abstract {
85 6     6 0 11 my ($self) = @_;
86              
87 6         12 my $parser = $self->parser;
88 6         14 my $abstract = $parser->abstract;
89              
90 6         11 return $self->head1('abstract', $abstract);
91             }
92              
93             sub construct_synopsis {
94 6     6 0 11 my ($self) = @_;
95              
96 6         13 my $parser = $self->parser;
97 6         12 my $synopsis = $parser->synopsis;
98              
99 6         14 return $self->head1('synopsis', $synopsis);
100             }
101              
102             sub construct_description {
103 6     6 0 13 my ($self) = @_;
104              
105 6         10 my $parser = $self->parser;
106 6         13 my $description = $parser->description;
107              
108 6         16 return $self->head1('description', $description);
109             }
110              
111             sub construct_inherits {
112 6     6 0 12 my ($self) = @_;
113              
114 6         10 my $parser = $self->parser;
115 6         14 my $inherits = $parser->inherits;
116              
117 6 50 33     19 return () if !$inherits || !@$inherits;
118              
119 0         0 my @content;
120              
121 0         0 push @content, $self->head1('inherits', [
122             "This package inherits behaviors from:",
123             "", join "\n\n", map "L<$_>", @$inherits
124             ]);
125              
126 0         0 return join("\n", @content);
127             }
128              
129             sub construct_integrates {
130 6     6 0 13 my ($self) = @_;
131              
132 6         11 my $parser = $self->parser;
133 6         13 my $integrates = $parser->integrates;
134              
135 6 50 33     16 return () if !$integrates || !@$integrates;
136              
137 0         0 my @content;
138              
139 0         0 push @content, $self->head1('integrates', [
140             "This package integrates behaviors from:",
141             "", join "\n\n", map "L<$_>", @$integrates
142             ]);
143              
144 0         0 return join("\n", @content);
145             }
146              
147             sub construct_libraries {
148 6     6 0 12 my ($self) = @_;
149              
150 6         14 my $parser = $self->parser;
151 6         13 my $libraries = $parser->libraries;
152              
153 6 50 33     51 return () if !$libraries || !@$libraries;
154              
155 6         10 my @content;
156              
157 6         38 push @content, $self->head1('libraries', [
158             "This package uses type constraints from:",
159             "", join "\n\n", map "L<$_>", @$libraries
160             ]);
161              
162 6         23 return join("\n", @content);
163             }
164              
165             sub construct_constraints {
166 6     6 0 11 my ($self) = @_;
167              
168 6         15 my $parser = $self->parser;
169 6         20 my $types = $parser->types;
170              
171 6 50 33     35 return () if !$types || !%$types;
172              
173 0         0 my @content;
174              
175 0         0 push @content, $self->head1('constraints', [
176             "This package declares the following type constraints:",
177             ]);
178              
179 0         0 my @order = sort keys %$types;
180              
181 0         0 push @content, $self->construct_constraints_item($_) for @order;
182              
183 0         0 return join("\n", @content);
184             }
185              
186             sub construct_constraints_item {
187 0     0 0 0 my ($self, $name) = @_;
188              
189 0         0 my $label = lc $name;
190 0         0 my $parser = $self->parser;
191 0         0 my $types = $parser->types;
192 0 0       0 my $type = $types->{$name} or return ();
193              
194 0         0 my @content;
195              
196 0         0 my $usage = $type->{usage}[0];
197 0 0       0 my $library = $type->{library}[0] if $type->{library};
198 0 0       0 my $composite = $type->{composite}[0] if $type->{composite};
199 0 0       0 my $parent = $type->{parent}[0] if $type->{parent};
200              
201 0         0 push @content, @$usage;
202              
203 0 0       0 if ($library) {
204 0         0 $library = $library->[0];
205 0         0 push @content, "", "This type is defined in the L<$library> library.";
206             }
207              
208 0 0       0 if ($parent) {
209 0         0 push @content, $self->over($self->item(
210             "$label parent", join "\n", @$parent
211             ));
212             }
213              
214 0 0       0 if ($composite) {
215 0         0 push @content, $self->over($self->item(
216             "$label composition", join "\n", @$composite
217             ));
218             }
219              
220 0 0       0 if (my $coercions = $type->{coercions}) {
221 0         0 for my $number (sort keys %{$coercions}) {
  0         0  
222 0         0 my $coercion = $coercions->{$number}[0];
223 0         0 push @content, $self->over($self->item(
224             "$label coercion #$number", join "\n", @$coercion
225             ));
226             }
227             }
228              
229 0 0       0 if (my $examples = $type->{examples}) {
230 0         0 for my $number (sort keys %{$examples}) {
  0         0  
231 0         0 my $example = $examples->{$number}[0];
232 0         0 push @content, $self->over($self->item(
233             "$label example #$number", join "\n", @$example
234             ));
235             }
236             }
237              
238 0         0 return $self->head2($name, [@content]);
239             }
240              
241             sub construct_scenarios {
242 6     6 0 14 my ($self) = @_;
243              
244 6         14 my $parser = $self->parser;
245 6         23 my $scenarios = $parser->scenarios;
246              
247 6 50 33     29 return () if !$scenarios || !%$scenarios;
248              
249 6         10 my @content;
250              
251 6         18 push @content, $self->head1('scenarios', [
252             "This package supports the following scenarios:"
253             ]);
254              
255 6         24 my @order = sort keys %$scenarios;
256              
257 6         31 push @content, $self->construct_scenarios_item($_) for @order;
258              
259 6         35 return join("\n", @content);
260             }
261              
262             sub construct_scenarios_item {
263 6     6 0 16 my ($self, $name) = @_;
264              
265 6         11 my $parser = $self->parser;
266 6         15 my $scenarios = $parser->scenarios;
267 6 50       27 my $scenario = $scenarios->{$name} or return ();
268              
269 6         9 my $usage = $scenario->{usage};
270 6         14 my $example = $scenario->{example}[0];
271              
272 6         29 return $self->head2($name, [@$example, "", @$usage]);
273             }
274              
275             sub construct_attributes {
276 6     6 0 14 my ($self) = @_;
277              
278 6         13 my $parser = $self->parser;
279 6         11 my $attributes = $parser->attributes;
280              
281 6 50 33     27 return () if !$attributes || !@$attributes;
282              
283 6         10 my @content;
284              
285             push @content, $self->head1('attributes', [
286             "This package has the following attributes:"
287             ]),
288             join "\n", map $self->construct_attributes_item($_),
289 6         14 sort keys %{$parser->stash('attributes')};
  6         16  
290              
291 6         23 return join("\n", @content);
292             }
293              
294             sub construct_attributes_item {
295 12     12 0 31 my ($self, $name) = @_;
296              
297 12         32 my $parser = $self->parser;
298 12         30 my $attributes = $parser->stash('attributes');
299 12 50       31 my $attribute = $attributes->{$name} or return ();
300              
301 12         20 my $is = $attribute->{is};
302 12         20 my $type = $attribute->{type};
303 12         18 my $presence = $attribute->{presence};
304              
305 12 50       27 $is = "read-only" if $is eq 'ro';
306 12 50       23 $is = "read-write" if $is eq 'rw';
307              
308 12 100       32 $presence = "required" if $presence eq 'req';
309 12 100       26 $presence = "optional" if $presence eq 'opt';
310              
311 12         51 return $self->head2($name, [
312             " $name($type)\n",
313             "This attribute is $is, accepts C<($type)> values, and is $presence."
314             ]);
315             }
316              
317             sub construct_headers {
318 6     6 0 14 my ($self) = @_;
319              
320 6         12 my $parser = $self->parser;
321 6         13 my $headers = $parser->headers;
322              
323 6 50 33     35 return () if !$headers || !@$headers;
324              
325 6         32 return join("\n", "", @$headers);
326             }
327              
328             sub construct_functions {
329 6     6 0 12 my ($self) = @_;
330              
331 6         14 my $parser = $self->parser;
332 6         26 my $functions = $parser->functions;
333              
334 6 50 33     29 return () if !$functions || !%$functions;
335              
336 6         10 my @content;
337              
338 6         15 push @content, $self->head1('functions', [
339             "This package implements the following functions:"
340             ]);
341              
342 6         21 my @order = sort keys %$functions;
343              
344 6         20 push @content, $self->construct_functions_item($_) for @order;
345              
346 6         22 return join("\n", @content);
347             }
348              
349             sub construct_functions_item {
350 6     6 0 34 my ($self, $name) = @_;
351              
352 6         19 my $parser = $self->parser;
353 6         14 my $functions = $parser->functions;
354 6 50       27 my $function = $functions->{$name} or return ();
355              
356 6         106 my @examples;
357              
358 6         16 my $usage = $function->{usage}[0];
359 6         79 my $signature = $function->{signature}[0];
360              
361 6         13 for my $number (sort keys %{$function->{examples}}) {
  6         23  
362 6         11 my $example = $function->{examples}{$number}[0];
363 6         24 my @content = ("$name example #$number", join "\n", @$example);
364 6         20 push @examples, $self->over($self->item(@content));
365             }
366              
367 6         29 return $self->head2($name, [" $$signature[0]", "", @$usage, @examples]);
368             }
369              
370             sub construct_routines {
371 6     6 0 15 my ($self) = @_;
372              
373 6         13 my $parser = $self->parser;
374 6         20 my $routines = $parser->routines;
375              
376 6 50 33     41 return () if !$routines || !%$routines;
377              
378 0         0 my @content;
379              
380 0         0 push @content, $self->head1('routines', [
381             "This package implements the following routines:"
382             ]);
383              
384 0         0 my @order = sort keys %$routines;
385              
386 0         0 push @content, $self->construct_routines_item($_) for @order;
387              
388 0         0 return join("\n", @content);
389             }
390              
391             sub construct_routines_item {
392 0     0 0 0 my ($self, $name) = @_;
393              
394 0         0 my $parser = $self->parser;
395 0         0 my $routines = $parser->routines;
396 0 0       0 my $routine = $routines->{$name} or return ();
397              
398 0         0 my @examples;
399              
400 0         0 my $usage = $routine->{usage}[0];
401 0         0 my $signature = $routine->{signature}[0];
402              
403 0         0 for my $number (sort keys %{$routine->{examples}}) {
  0         0  
404 0         0 my $example = $routine->{examples}{$number}[0];
405 0         0 my @content = ("$name example #$number", join "\n", @$example);
406 0         0 push @examples, $self->over($self->item(@content));
407             }
408              
409 0         0 return $self->head2($name, [" $$signature[0]", "", @$usage, @examples]);
410             }
411              
412             sub construct_methods {
413 6     6 0 11 my ($self) = @_;
414              
415 6         14 my $parser = $self->parser;
416 6         14 my $methods = $parser->methods;
417              
418 6 50 33     26 return () if !$methods || !%$methods;
419              
420 6         9 my @content;
421              
422 6         16 push @content, $self->head1('methods', [
423             "This package implements the following methods:"
424             ]);
425              
426 6         31 my @order = sort keys %$methods;
427              
428 6         32 push @content, $self->construct_methods_item($_) for @order;
429              
430 6         37 return join("\n", @content);
431             }
432              
433             sub construct_methods_item {
434 18     18 0 36 my ($self, $name) = @_;
435              
436 18         35 my $parser = $self->parser;
437 18         37 my $methods = $parser->methods;
438 18 50       58 my $method = $methods->{$name} or return ();
439              
440 18         23 my @examples;
441              
442 18         26 my $usage = $method->{usage}[0];
443 18         26 my $signature = $method->{signature}[0];
444              
445 18         25 for my $number (sort keys %{$method->{examples}}) {
  18         49  
446 18         31 my $example = $method->{examples}{$number}[0];
447 18         61 my @content = ("$name example #$number", join "\n", @$example);
448 18         39 push @examples, $self->over($self->item(@content));
449             }
450              
451 18         61 return $self->head2($name, [" $$signature[0]", "", @$usage, @examples]);
452             }
453              
454             sub construct_footers {
455 6     6 0 10 my ($self) = @_;
456              
457 6         14 my $parser = $self->parser;
458 6         14 my $footers = $parser->footers;
459              
460 6 50 33     38 return () if !$footers || !@$footers;
461              
462 6         384 return join("\n", "", @$footers);
463             }
464              
465             sub render {
466 3     3 1 8 my ($self) = @_;
467              
468 3         9 my $content = $self->content;
469              
470 3         69 $content = join "\n", @$content;
471 3         5013 $content =~ s/^\n+|\n+$//g;
472              
473             # unescape nested pod
474 3         309 $content =~ s/^\+=\s*(.+?)\s*(\r?\n)/=$1$2\n/mg;
475 3         129 $content =~ s/^\+=cut\r?\n?$/=cut/m;
476              
477             # process template (if applicable)
478 3         15 $content = $self->templated($content);
479              
480             # add leading newline to assist coalescing
481 3         163 return "\n$content";
482             }
483              
484             sub templated {
485 3     3 0 103 my ($self, $content) = @_;
486              
487 3   100     27 my $template = $self->template || $ENV{TEST_AUTO_TEMPLATE};
488              
489 3 100       12 return $content unless $template;
490              
491 2 50       90 open my $fh, "<", $template or Carp::confess "Can't open $template: $!";
492              
493 2         50 my $output = join "", <$fh>;
494              
495 2         24 close $fh;
496              
497 2         77 $output =~ s/\{content\}/$content/;
498              
499 2         57 return $output;
500             }
501              
502             sub over {
503 24     24 0 45 my ($self, @items) = @_;
504              
505 24         86 return join("\n", "", "=over 4", "", @items, "=back");
506             }
507              
508             sub item {
509 24     24 0 40 my ($self, $name, $data) = @_;
510              
511 24         71 return ("=item $name\n", "$data\n");
512             }
513              
514             sub head1 {
515 54     54 0 147 my ($self, $name, $data) = @_;
516              
517 54         95 return join("\n", "", "=head1 \U$name", "", @{$data}, "", "=cut");
  54         204  
518             }
519              
520             sub head2 {
521 42     42 0 72 my ($self, $name, $data) = @_;
522              
523 42         73 return join("\n", "", "=head2 \L$name", "", @{$data}, "", "=cut");
  42         216  
524             }
525              
526             1;
527              
528             =encoding utf8
529              
530             =head1 NAME
531              
532             Test::Auto::Document
533              
534             =cut
535              
536             =head1 ABSTRACT
537              
538             Documentation Generator
539              
540             =cut
541              
542             =head1 SYNOPSIS
543              
544             package main;
545              
546             use Test::Auto;
547             use Test::Auto::Parser;
548             use Test::Auto::Document;
549              
550             my $test = Test::Auto->new(
551             't/Test_Auto.t'
552             );
553              
554             my $parser = Test::Auto::Parser->new(
555             source => $test
556             );
557              
558             my $doc = Test::Auto::Document->new(
559             parser => $parser
560             );
561              
562             # render documentation
563              
564             # $doc->render
565              
566             =cut
567              
568             =head1 DESCRIPTION
569              
570             This package use the L<Test::Auto::Parser> object to generate a valid Perl 5
571             POD document.
572              
573             =cut
574              
575             =head1 LIBRARIES
576              
577             This package uses type constraints from:
578              
579             L<Test::Auto::Types>
580              
581             =cut
582              
583             =head1 ATTRIBUTES
584              
585             This package has the following attributes:
586              
587             =cut
588              
589             =head2 content
590              
591             content(ArrayRef[Str])
592              
593             This attribute is read-only, accepts C<(ArrayRef[Str])> values, and is optional.
594              
595             =cut
596              
597             =head2 parser
598              
599             parser(Parser)
600              
601             This attribute is read-only, accepts C<(Parser)> values, and is required.
602              
603             =cut
604              
605             =head2 template
606              
607             template(Maybe[Str])
608              
609             This attribute is read-only, accepts C<(Maybe[Str])> values, and is optional.
610              
611             =cut
612              
613             =head1 METHODS
614              
615             This package implements the following methods:
616              
617             =cut
618              
619             =head2 render
620              
621             render() : Str
622              
623             This method returns a string representation of a valid POD document. You can
624             also provide a template to wrap the generated document by passing it to the
625             constructor or specifying it in the C<TEST_AUTO_TEMPLATE> environment variable.
626              
627             =over 4
628              
629             =item render example #1
630              
631             # given: synopsis
632              
633             my $rendered = $doc->render;
634              
635             =back
636              
637             =over 4
638              
639             =item render example #2
640              
641             # given: synopsis
642              
643             $ENV{TEST_AUTO_TEMPLATE} = './t/Test_Template.pod';
644              
645             # where ./t/Test_Template.pod has a {content} placeholder
646              
647             my $rendered = $doc->render;
648              
649             undef $ENV{TEST_AUTO_TEMPLATE};
650              
651             $rendered;
652              
653             =back
654              
655             =over 4
656              
657             =item render example #3
658              
659             # given: synopsis
660              
661             my $tmpl = Test::Auto::Document->new(
662             parser => $parser,
663             template => './t/Test_Template.pod'
664             );
665              
666             my $rendered = $tmpl->render;
667              
668             =back
669              
670             =cut
671              
672             =head1 AUTHOR
673              
674             Al Newkirk, C<awncorp@cpan.org>
675              
676             =head1 LICENSE
677              
678             Copyright (C) 2011-2019, Al Newkirk, et al.
679              
680             This is free software; you can redistribute it and/or modify it under the terms
681             of the The Apache License, Version 2.0, as elucidated in the
682             L<"license file"|https://github.com/iamalnewkirk/test-auto/blob/master/LICENSE>.
683              
684             =head1 PROJECT
685              
686             L<Wiki|https://github.com/iamalnewkirk/test-auto/wiki>
687              
688             L<Project|https://github.com/iamalnewkirk/test-auto>
689              
690             L<Initiatives|https://github.com/iamalnewkirk/test-auto/projects>
691              
692             L<Milestones|https://github.com/iamalnewkirk/test-auto/milestones>
693              
694             L<Issues|https://github.com/iamalnewkirk/test-auto/issues>
695              
696             =cut