File Coverage

blib/lib/App/DocKnot/Generate.pm
Criterion Covered Total %
statement 249 256 97.2
branch 36 44 81.8
condition 6 9 66.6
subroutine 32 32 100.0
pod 4 4 100.0
total 327 345 94.7


line stmt bran cond sub pod time code
1             # Generate human-readable documentation from package metadata.
2             #
3             # This is the implementation of the docknot generate command, which uses
4             # templates to support generation of various documentation files based on
5             # package metadata.
6             #
7             # SPDX-License-Identifier: MIT
8              
9             ##############################################################################
10             # Modules and declarations
11             ##############################################################################
12              
13             package App::DocKnot::Generate 2.00;
14              
15 6     6   103 use 5.024;
  6         40  
16 6     6   33 use autodie;
  6         10  
  6         30  
17 6     6   31370 use warnings;
  6         13  
  6         281  
18              
19 6     6   35 use Carp qw(croak);
  6         12  
  6         388  
20 6     6   3065 use File::BaseDir qw(config_files);
  6         7959  
  6         391  
21 6     6   2498 use File::ShareDir qw(module_file);
  6         124859  
  6         408  
22 6     6   50 use File::Spec;
  6         12  
  6         149  
23 6     6   3501 use JSON;
  6         51431  
  6         42  
24 6     6   2344 use Perl6::Slurp;
  6         3931  
  6         49  
25 6     6   3232 use Template;
  6         115366  
  6         224  
26 6     6   2835 use Text::Wrap qw(wrap);
  6         15688  
  6         23141  
27              
28             # Default output files for specific templates.
29             my %DEFAULT_OUTPUT = (
30             'readme' => 'README',
31             'readme-md' => 'README.md',
32             );
33              
34             ##############################################################################
35             # Generator functions
36             ##############################################################################
37              
38             # The internal helper object methods in this section are generators. They
39             # return code references intended to be passed into Template Toolkit as code
40             # references so that they can be called inside templates, incorporating data
41             # from the App::DocKnot configuration or the package metadata.
42              
43             # Returns code to center a line in $self->{width} characters given the text of
44             # the line. The returned code will take a line of text and return that line
45             # with leading whitespace added as required.
46             #
47             # $self - The App::DocKnot::Generate object
48             #
49             # Returns: Code reference to a closure that uses $self->{width} for width
50             sub _code_for_center {
51 34     34   121 my ($self) = @_;
52             my $center = sub {
53 42     42   122 my ($text) = @_;
54 42         160 my $space = $self->{width} - length($text);
55 42 50       121 if ($space <= 0) {
56 0         0 return $text;
57             } else {
58 42         320 return q{ } x int($space / 2) . $text;
59             }
60 34         212 };
61 34         118 return $center;
62             }
63              
64             # Returns code that formats the copyright notices for the package. The
65             # resulting code reference takes two parameter, the indentation level and an
66             # optional prefix for each line, and wraps the copyright notices accordingly.
67             # They will be wrapped with a four-space outdent and kept within
68             # $self->{width} columns.
69             #
70             # $self - The App::DocKnot::Generate object
71             # $copyrights_ref - A reference to a list of anonymous hashes, each with keys:
72             # holder - The copyright holder for that copyright
73             # years - The years of that copyright
74             #
75             # Returns: Code reference to a closure taking an indent level and an optional
76             # prefix and returning the formatted copyright notice
77             sub _code_for_copyright {
78 34     34   133 my ($self, $copyrights_ref) = @_;
79             my $copyright = sub {
80 14     14   61 my ($indent, $lead) = @_;
81 14   50     119 my $prefix = ($lead // q{}) . q{ } x $indent;
82 14         31 my $notice;
83 14         48 for my $copyright ($copyrights_ref->@*) {
84 24         63 my $holder = $copyright->{holder};
85 24         59 my $years = $copyright->{years};
86              
87             # Build the initial notice with the word copyright and the years.
88 24         69 my $text = 'Copyright ' . $copyright->{years};
89 24         86 local $Text::Wrap::columns = $self->{width} + 1;
90 24         54 local $Text::Wrap::unexpand = 0;
91 24         161 $text = wrap($prefix, $prefix . q{ } x 4, $text);
92              
93             # See if the holder fits on the last line. If so, add it there;
94             # otherwise, add another line.
95 24         4228 my $last_length;
96 24 50       153 if (rindex($text, "\n") == -1) {
97 24         49 $last_length = length($text);
98             } else {
99 0         0 $last_length = length($text) - rindex($text, "\n");
100             }
101 24 100       91 if ($last_length + length($holder) < $self->{width}) {
102 17         80 $text .= " $holder";
103             } else {
104 7         27 $text .= "\n" . $prefix . q{ } x 4 . $holder;
105             }
106 24         96 $notice .= $text . "\n";
107             }
108 14         53 chomp($notice);
109 14         75 return $notice;
110 34         204 };
111 34         99 return $copyright;
112             }
113              
114             # Returns code to indent each line of a paragraph by a given number of spaces.
115             # This is constructed as a method returning a closure so that its behavior can
116             # be influenced by App::DocKnot configuration in the future, but it currently
117             # doesn't use any configuration. It takes the indentation and an optional
118             # prefix to put at the start of each line.
119             #
120             # $self - The App::DocKnot::Generate object
121             #
122             # Returns: Code reference to a closure
123             sub _code_for_indent {
124 34     34   105 my ($self) = @_;
125             my $indent = sub {
126 121     121   351 my ($text, $space, $lead) = @_;
127 121   100     586 $lead //= q{};
128 121         1176 my @text = split(m{\n}xms, $text);
129 121         314 return join("\n", map { $lead . q{ } x $space . $_ } @text);
  2537         6461  
130 34         148 };
131 34         115 return $indent;
132             }
133              
134             # Returns code that converts metadata text (which is assumed to be in
135             # Markdown) to text. This is not a complete Markdown formatter. It only
136             # supports the bits of markup that I've had some reason to use.
137             #
138             # This is constructed as a method returning a closure so that its behavior can
139             # be influenced by App::DocKnot configuration in the future, but it currently
140             # doesn't use any configuration.
141             #
142             # $self - The App::DocKnot::Generate object
143             #
144             # Returns: Code reference to a closure that takes a block of text and returns
145             # the coverted text
146             sub _code_for_to_text {
147 34     34   104 my ($self) = @_;
148             my $to_text = sub {
149 78     78   236 my ($text) = @_;
150              
151             # Remove triple backticks but escape all backticks inside them.
152 78         706 $text =~ s{ ``` \w* (\s .*?) ``` }{
153 35         105 my $text = $1;
154 35         59 $text =~ s{ [\`] }{``}xmsg;
155 35         203 $text;
156             }xmsge;
157              
158             # Remove backticks, but don't look at things starting with doubled
159             # backticks.
160 78         921 $text =~ s{ (?
161              
162             # Undo backtick escaping.
163 78         175 $text =~ s{ `` }{\`}xmsg;
164              
165             # Rewrite quoted paragraphs to have four spaces of additional
166             # indentation.
167 78         156 $text =~ s{
168             \n \n # start of paragraph
169             ( # start of the text
170             (> \s+) # quote mark on first line
171             \S [^\n]* \n # first line
172             (?: # all subsequent lines
173             \2 \S [^\n]* \n # start with the same prefix
174             )* # any number of subsequent lines
175             ) # end of the text
176             }{
177 1         7 my ($text, $prefix) = ($1, $2);
178 1         41 $text =~ s{ ^ \Q$prefix\E }{ }xmsg;
179 1         20 "\n\n" . $text;
180             }xmsge;
181              
182             # For each paragraph, remove URLs from all links, replacing them with
183             # numeric references, and accumulate the mapping of numbers to URLs in
184             # %urls. Then, add to the end of the paragraph the references and
185             # URLs.
186 78         137 my $ref = 1;
187 78         416 my @paragraphs = split(m{ \n\n }xms, $text);
188 78         194 for my $para (@paragraphs) {
189 361         456 my %urls;
190 361         827 while ($para =~ s{ \[ ([^\]]+) \] [(] (\S+) [)] }{$1 [$ref]}xms) {
191 12         53 $urls{$ref} = $2;
192 12         56 $ref++;
193             }
194 361 100       713 if (%urls) {
195 10         49 my @refs = map { "[$_] $urls{$_}" } sort { $a <=> $b }
  12         59  
  2         10  
196             keys(%urls);
197 10         85 $para .= "\n\n" . join("\n", q{}, @refs, q{});
198             }
199             }
200              
201             # Rejoin the paragraphs and return the result.
202 78         1045 return join("\n\n", @paragraphs);
203 34         171 };
204 34         105 return $to_text;
205             }
206              
207             # Returns code that converts metadata text (which is assumed to be in
208             # Markdown) to thread. This is not a complete Markdown formatter. It only
209             # supports the bits of markup that I've had some reason to use.
210             #
211             # This is constructed as a method returning a closure so that its behavior can
212             # be influenced by App::DocKnot configuration in the future, but it currently
213             # doesn't use any configuration.
214             #
215             # $self - The App::DocKnot::Generate object
216             #
217             # Returns: Code reference to a closure that takes a block of text and returns
218             # the coverted thread
219             sub _code_for_to_thread {
220 34     34   131 my ($self) = @_;
221             my $to_thread = sub {
222 39     39   99 my ($text) = @_;
223              
224             # Escape all backslashes.
225 39         117 $text =~ s{ \\ }{\\\\}xmsg;
226              
227             # Rewrite triple backticks to \pre blocks and escape backticks inside
228             # them so that they're not turned into \code blocks.
229 39         103 $text =~ s{ ``` \w* (\s .*?) ``` }{
230 0         0 my $text = $1;
231 0         0 $text =~ s{ [\`] }{``}xmsg;
232 0         0 '\pre[' . $1 . ']';
233             }xmsge;
234              
235             # Rewrite backticks to \code blocks.
236 39         178 $text =~ s{ ` ([^\`]+) ` }{\\code[$1]}xmsg;
237              
238             # Undo backtick escaping.
239 39         79 $text =~ s{ `` }{\`}xmsg;
240              
241             # Rewrite all Markdown links into thread syntax.
242 39         164 $text =~ s{ \[ ([^\]]+) \] [(] (\S+) [)] }{\\link[$2][$1]}xmsg;
243              
244             # Rewrite long bullets. This is quite tricky since we have to grab
245             # every line from the first bulleted one to the point where the
246             # indentation stops.
247 39         138 $text =~ s{
248             ( # capture whole contents
249             ^ (\s*) # indent before bullet
250             [*] (\s+) # bullet and following indent
251             [^\n]+ \n # rest of line
252             (?: \s* \n )* # optional blank lines
253             (\2 [ ] \3) # matching indent
254             [^\n]+ \n # rest of line
255             (?: # one or more of
256             \4 # matching indent
257             [^\n]+ \n # rest of line
258             | # or
259             \s* \n # blank lines
260             )+ # end of indented block
261             ) # full bullet with leading bullet
262             }{
263 3         10 my $text = $1;
264 3         10 $text =~ s{ [*] }{ }xms;
265 3         18 "\\bullet[\n\n" . $text . "\n]\n";
266             }xmsge;
267              
268             # Do the same thing, but with numbered lists. This doesn't handle
269             # numbers larger than 9 currently, since that requires massaging the
270             # spacing.
271 39         277 $text =~ s{
272             ( # capture whole contents
273             ^ (\s*) # indent before number
274             \d [.] (\s+) # number and following indent
275             [^\n]+ \n # rest of line
276             (?: \s* \n )* # optional blank lines
277             (\2 [ ][ ] \3) # matching indent
278             [^\n]+ \n # rest of line
279             (?: # one or more of
280             \4 # matching indent
281             [^\n]+ \n # rest of line
282             | # or
283             \s* \n # blank lines
284             )+ # end of indented block
285             ) # full bullet with leading bullet
286             }{
287 3         10 my $text = $1;
288 3         18 $text =~ s{ \A (\s*) \d [.] }{$1 }xms;
289 3         30 "\\number[\n\n" . $text . "\n]\n\n";
290             }xmsge;
291              
292             # Rewrite compact bulleted lists.
293 39         130 $text =~ s{ \n ( (?: \s* [*] \s+ [^\n]+ \s* \n ){2,} ) }{
294 5         22 my $list = $1;
295 5         75 $list =~ s{ \n [*] \s+ ([^\n]+) }{\n\\bullet(packed)[$1]}xmsg;
296 5         29 "\n" . $list;
297             }xmsge;
298              
299             # Done. Return the results.
300 39         215 return $text;
301 34         168 };
302 34         91 return $to_thread;
303             }
304              
305             ##############################################################################
306             # Helper methods
307             ##############################################################################
308              
309             # Internal helper routine to return the path of a file from the application
310             # data. These data files are installed with App::DocKnot, but each file can
311             # be overridden by the user via files in $HOME/.config/docknot or
312             # /etc/xdg/docknot (or whatever $XDG_CONFIG_DIRS is set to).
313             #
314             # We therefore try File::BaseDir first (which handles the XDG paths) and fall
315             # back on using File::ShareDir to locate the data.
316             #
317             # $self - The App::DocKnot::Generate object
318             # @path - The relative path of the file as a list of components
319             #
320             # Returns: The absolute path to the application data
321             # Throws: Text exception on failure to locate the desired file
322             sub _appdata_path {
323 102     102   300 my ($self, @path) = @_;
324              
325             # Try XDG paths first.
326 102         435 my $path = config_files('docknot', @path);
327              
328             # If that doesn't work, use the data that came with the module.
329 102 50       7581 if (!defined($path)) {
330 102         907 $path = module_file('App::DocKnot', File::Spec->catfile(@path));
331             }
332 102         30033 return $path;
333             }
334              
335             # Internal helper routine that locates an application data file, interprets it
336             # as JSON, and returns the resulting decoded contents. This uses the relaxed
337             # parsing mode, so comments and commas after data elements are supported.
338             #
339             # $self - The App::DocKnot::Generate object
340             # @path - The path of the file to load, as a list of components
341             #
342             # Returns: Anonymous hash or array resulting from decoding the JSON object
343             # Throws: slurp or JSON exception on failure to load or decode the object
344             sub _load_appdata_json {
345 34     34   126 my ($self, @path) = @_;
346 34         162 my $path = $self->_appdata_path(@path);
347 34         238 my $json = JSON->new;
348 34         157 $json->relaxed;
349 34         134 return $json->decode(scalar(slurp($path)));
350             }
351              
352             # Internal helper routine to return the path of a file or directory from the
353             # package metadata directory. The resulting file or directory path is not
354             # checked for existence.
355             #
356             # $self - The App::DocKnot::Generate object
357             # @path - The relative path of the file as a list of components
358             #
359             # Returns: The absolute path in the metadata directory
360             sub _metadata_path {
361 505     505   1132 my ($self, @path) = @_;
362 505         5316 return File::Spec->catdir($self->{metadata}, @path);
363             }
364              
365             # Internal helper routine to read a file from the package metadata directory
366             # and return the contents. The file is specified as a list of path
367             # components.
368             #
369             # $self - The App::DocKnot::Generate object
370             # @path - The path of the file to load, as a list of components
371             #
372             # Returns: The contents of the file as a string
373             # Throws: slurp exception on failure to read the file
374             sub _load_metadata {
375 505     505   1342 my ($self, @path) = @_;
376 505         1333 return slurp($self->_metadata_path(@path));
377             }
378              
379             # Like _load_metadata, but interprets the contents of the metadata file as
380             # JSON and decodes it, returning the resulting object. This uses the relaxed
381             # parsing mode, so comments and commas after data elements are supported.
382             #
383             # $self - The App::DocKnot::Generate object
384             # @path - The path of the file to load, as a list of components
385             #
386             # Returns: Anonymous hash or array resulting from decoding the JSON object
387             # Throws: slurp or JSON exception on failure to load or decode the object
388             sub _load_metadata_json {
389 34     34   118 my ($self, @path) = @_;
390 34         149 my $data = $self->_load_metadata(@path);
391 34         6918 my $json = JSON->new;
392 34         254 $json->relaxed;
393 34         1680 return $json->decode($data);
394             }
395              
396             # Word-wrap a paragraph of text. This is a helper function for _wrap, mostly
397             # so that it can be invoked recursively to wrap bulleted paragraphs.
398             #
399             # If the paragraph looks like regular text, which means indented by two or
400             # four spaces and consistently on each line, remove the indentation and then
401             # add it back in while wrapping the text.
402             #
403             # $self - The App::DocKnot::Generate object
404             # $paragraph - A paragraph of text to wrap
405             #
406             # Returns: The wrapped paragraph
407             sub _wrap_paragraph {
408 2161     2161   3689 my ($self, $paragraph) = @_;
409 2161         6424 my ($indent) = ($paragraph =~ m{ \A ([ ]*) \S }xms);
410              
411             # If the indent is longer than four characters, leave it alone.
412 2161 100       5058 if (length($indent) > 4) {
413 167         452 return $paragraph;
414             }
415              
416             # If this looks like thread commands or URLs, leave it alone.
417 1994 100       5045 if ($paragraph =~ m{ \A \s* (?: \\ | \[\d+\] ) }xms) {
418 219         539 return $paragraph;
419             }
420              
421             # If this starts with a bullet, strip the bullet off, wrap the paragaraph,
422             # and then add it back in.
423 1775 100       4735 if ($paragraph =~ s{ \A (\s*) [*] (\s+) }{$1 $2}xms) {
424 80         200 my $offset = length($1);
425 80         291 $paragraph = $self->_wrap_paragraph($paragraph);
426 80         249 substr($paragraph, $offset, 1, q{*});
427 80         251 return $paragraph;
428             }
429              
430             # If this looks like a Markdown block quote leave it alone, but strip
431             # trailing whitespace.
432 1695 100       3480 if ($paragraph =~ m{ \A \s* > \s }xms) {
433 13         261 $paragraph =~ s{ [ ]+ \n }{\n}xmsg;
434 13         55 return $paragraph;
435             }
436              
437             # If this looks like a bunch of short lines, leave it alone.
438 1682 100       3510 if ($paragraph =~ m{ \A [^\n]{1,40} \n [^\n]{1,40} \n }xms) {
439 85         256 return $paragraph;
440             }
441              
442             # If this paragraph is not consistently indented, leave it alone.
443 1597 100       13983 if ($paragraph !~ m{ \A (?: \Q$indent\E \S[^\n]+ \n )+ \z }xms) {
444 105         372 return $paragraph;
445             }
446              
447             # Strip the indent from each line.
448 1492         35975 $paragraph =~ s{ (?: \A | (?<=\n) ) \Q$indent\E }{}xmsg;
449              
450             # Remove any existing newlines, preserving two spaces after periods.
451 1492         4425 $paragraph =~ s{ [.] ([)\"]?) \n (\S) }{.$1 $2}xmsg;
452 1492         8467 $paragraph =~ s{ \n(\S) }{ $1}xmsg;
453              
454             # Force locally correct configuration of Text::Wrap.
455 1492         4383 local $Text::Wrap::break = qr{\s+}xms;
456 1492         3107 local $Text::Wrap::columns = $self->{width} + 1;
457 1492         2336 local $Text::Wrap::huge = 'overflow';
458 1492         2080 local $Text::Wrap::unexpand = 0;
459              
460             # Do the wrapping. This modifies @paragraphs in place.
461 1492         3613 $paragraph = wrap($indent, $indent, $paragraph);
462              
463             # Strip any trailing whitespace, since some gets left behind after periods
464             # by Text::Wrap.
465 1492         218453 $paragraph =~ s{ [ ]+ \n }{\n}xmsg;
466              
467             # All done.
468 1492         5418 return $paragraph;
469             }
470              
471             # Word-wrap a block of text. This requires some annoying heuristics, but the
472             # alternative is to try to get the template to always produce correctly
473             # wrapped results, which is far harder.
474             #
475             # $self - The App::DocKnot::Generate object
476             # $text - The text to wrap
477             #
478             # Returns: The wrapped text
479             sub _wrap {
480 34     34   183 my ($self, $text) = @_;
481              
482             # First, break the text up into paragraphs. (This will also turn more
483             # than two consecutive newlines into just two newlines.)
484 34         3281 my @paragraphs = split(m{ \n(?:[ ]*\n)+ }xms, $text);
485              
486             # Add back the trailing newlines at the end of each paragraph.
487 34         174 @paragraphs = map { $_ . "\n" } @paragraphs;
  2081         4107  
488              
489             # Wrap all of the paragraphs. This modifies @paragraphs in place.
490 34         153 for my $paragraph (@paragraphs) {
491 2081         4492 $paragraph = $self->_wrap_paragraph($paragraph);
492             }
493              
494             # Glue the paragraphs back together and return the result. Because the
495             # last newline won't get stripped by the split above, we have to strip an
496             # extra newline from the end of the file.
497 34         740 my $result = join("\n", @paragraphs);
498 34         2440 $result =~ s{ \n+ \z }{\n}xms;
499 34         831 return $result;
500             }
501              
502             ##############################################################################
503             # Public interface
504             ##############################################################################
505              
506             # Create a new App::DocKnot::Generate object, which will be used for
507             # subsequent calls.
508             #
509             # $class - Class of object to create
510             # $args - Anonymous hash of arguments with the following keys:
511             # metadata - Path to the directory containing package metadata
512             # width - Line length at which to wrap output files
513             #
514             # Returns: Newly created object
515             # Throws: Text exceptions on invalid metadata directory path
516             sub new {
517 14     14 1 13224 my ($class, $args_ref) = @_;
518              
519             # Ensure we were given a valid metadata argument.
520 14         50 my $metadata = $args_ref->{metadata};
521 14 100       59 if (!defined($metadata)) {
522 3         6 $metadata = 'docs/metadata';
523             }
524 14 100       385 if (!-d $metadata) {
525 1         207 croak("metadata path $metadata does not exist or is not a directory");
526             }
527              
528             # Create and return the object.
529             my $self = {
530             metadata => $metadata,
531 13   50     134 width => $args_ref->{width} // 74,
532             };
533 13         43 bless($self, $class);
534 13         52 return $self;
535             }
536              
537             # Generate a documentation file from the package metadata.
538             #
539             # $self - The App::DocKnot::Generate object
540             # $template - Name of the documentation template (using Template Toolkit)
541             #
542             # Returns: The generated documentation as a string
543             # Throws: autodie exception on failure to read metadata or write the output
544             # Text exception on Template Toolkit failures
545             # Text exception on inconsistencies in the package data
546             sub generate {
547 34     34 1 28050 my ($self, $template) = @_;
548              
549             # Load the package metadata from JSON.
550 34         160 my $data_ref = $self->_load_metadata_json('metadata.json');
551              
552             # Load supplemental README sections. readme.sections will contain a list
553             # of sections to add to the README file.
554 34         204 for my $section ($data_ref->{readme}{sections}->@*) {
555 45         107 my $title = $section->{title};
556              
557             # The file containing the section data will match the title, converted
558             # to lowercase and with spaces changed to dashes.
559 45         127 my $file = lc($title);
560 45         123 $file =~ tr{ }{-};
561              
562             # Load the section content.
563 45         144 $section->{body} = $self->_load_metadata('sections', $file);
564              
565             # If this contains a testing section, that overrides our default. Set
566             # a flag so that the templates know this has happened.
567 45 100       6861 if ($file eq 'testing') {
568 6         30 $data_ref->{readme}{testing} = 1;
569             }
570             }
571              
572             # If the package is marked orphaned, load the explanation.
573 34 100       202 if ($data_ref->{orphaned}) {
574 3         36 $data_ref->{orphaned} = $self->_load_metadata('orphaned');
575             }
576              
577             # If the package has a quote, load the text of the quote.
578 34 100       591 if ($data_ref->{quote}) {
579 15         53 $data_ref->{quote}{text} = $self->_load_metadata('quote');
580             }
581              
582             # Expand the package license into license text.
583 34         2201 my $license = $data_ref->{license};
584 34         185 my $licenses_ref = $self->_load_appdata_json('licenses.json');
585 34 50       5782 if (!exists($licenses_ref->{$license})) {
586 0         0 die "Unknown license $license\n";
587             }
588 34         156 my $license_text = slurp($self->_appdata_path('licenses', $license));
589 34         5483 $data_ref->{license} = { $licenses_ref->{$license}->%* };
590 34         158 $data_ref->{license}{full} = $license_text;
591              
592             # Load additional license notices if they exist.
593 34         85 eval { $data_ref->{license}{notices} = $self->_load_metadata('notices') };
  34         131  
594              
595             # Create the variable information for the template. Start with all
596             # metadata as loaded above.
597 34         8735 my %vars = %{$data_ref};
  34         322  
598              
599             # Load the standard sections.
600 34         181 $vars{blurb} = $self->_load_metadata('blurb');
601 34         5119 $vars{description} = $self->_load_metadata('description');
602 34         4893 $vars{requirements} = $self->_load_metadata('requirements');
603              
604             # Load bootstrap, Debian summary information, and extra packaging
605             # information if they exist.
606 34         4772 eval { $vars{bootstrap} = $self->_load_metadata('bootstrap') };
  34         145  
607 34         6159 eval {
608 34         156 $vars{debian}{summary} = $self->_load_metadata('debian', 'summary');
609             };
610 34         5270 eval {
611 34         143 $vars{packaging}{extra} = $self->_load_metadata('packaging', 'extra');
612             };
613              
614             # Load build sections if they exist.
615 34         5277 eval { $vars{build}{middle} = $self->_load_metadata('build', 'middle') };
  34         157  
616 34         5248 eval { $vars{build}{suffix} = $self->_load_metadata('build', 'suffix') };
  34         141  
617              
618             # build.install defaults to true.
619 34 100       5228 if (!exists($vars{build}{install})) {
620 25         104 $vars{build}{install} = 1;
621             }
622              
623             # Load testing sections if they exist.
624 34         117 eval { $vars{test}{prefix} = $self->_load_metadata('test', 'prefix') };
  34         127  
625 34         5234 eval { $vars{test}{suffix} = $self->_load_metadata('test', 'suffix') };
  34         145  
626              
627             # Load support sections if they exist.
628 34         4973 eval { $vars{support}{extra} = $self->_load_metadata('support', 'extra') };
  34         134  
629              
630             # Add code references for our defined helper functions.
631 34         5354 $vars{center} = $self->_code_for_center;
632 34         168 $vars{copyright} = $self->_code_for_copyright($data_ref->{copyrights});
633 34         141 $vars{indent} = $self->_code_for_indent;
634 34         150 $vars{to_text} = $self->_code_for_to_text;
635 34         145 $vars{to_thread} = $self->_code_for_to_thread;
636              
637             # Ensure we were given a valid template.
638 34         151 $template = $self->_appdata_path('templates', "${template}.tmpl");
639              
640             # Run Template Toolkit processing.
641 34 50       458 my $tt = Template->new({ ABSOLUTE => 1 }) or croak(Template->error());
642 34         110624 my $result;
643 34 50       200 $tt->process($template, \%vars, \$result) or croak($tt->error);
644              
645             # Word-wrap the results to our width and return them.
646 34         5421 return $self->_wrap($result);
647             }
648              
649             # Generate all package documentation from the package metadata. Only
650             # generates the output for templates with a default output file.
651             #
652             # $self - The App::DocKnot::Generate object
653             #
654             # Returns: undef
655             # Throws: autodie exception on failure to read metadata or write the output
656             # Text exception on Template Toolkit failures
657             # Text exception on inconsistencies in the package data
658             sub generate_all {
659 2     2 1 1570 my ($self) = @_;
660 2         13 for my $template (keys(%DEFAULT_OUTPUT)) {
661 4         21 $self->generate_output($template);
662             }
663 2         15 return;
664             }
665              
666             # Generate a documentation file from the package metadata.
667             #
668             # $self - The App::DocKnot::Generate object
669             # $template - Name of the documentation template
670             # $output - Output file name (undef to use the default)
671             #
672             # Returns: undef
673             # Throws: autodie exception on failure to read metadata or write the output
674             # Text exception on Template Toolkit failures
675             # Text exception on inconsistencies in the package data
676             sub generate_output {
677 10     10 1 5217 my ($self, $template, $output) = @_;
678 10   66     66 $output //= $DEFAULT_OUTPUT{$template};
679              
680             # If the template doesn't have a default output file, $output is required.
681 10 50       51 if (!defined($output)) {
682 0         0 croak('missing required output argument');
683             }
684              
685             # Generate the output.
686 10         58 open(my $outfh, '>', $output);
687 10 50       7002 print {$outfh} $self->generate($template)
  10         58  
688             or croak("cannot write to $output: $!");
689 10         3652 close($outfh);
690 10         4063 return;
691             }
692              
693             ##############################################################################
694             # Module return value and documentation
695             ##############################################################################
696              
697             1;
698             __END__