File Coverage

blib/lib/Pod/Weaver/Plugin/Include/Finder.pm
Criterion Covered Total %
statement 105 107 98.1
branch 30 38 78.9
condition n/a
subroutine 19 19 100.0
pod 5 9 55.5
total 159 173 91.9


line stmt bran cond sub pod time code
1             #
2             package Pod::Weaver::Plugin::Include::Finder;
3              
4             our $VERSION = 'v0.1.5';
5              
6             our $VERSION = 'v0.1.901';
7              
8             # ABSTRACT: Finds source Pods in .pod files or modules.
9              
10              
11 2     2   590 use Pod::Find qw<pod_where>;
  2         4  
  2         125  
12 2     2   586 use File::Find::Rule;
  2         12366  
  2         20  
13 2     2   431 use Pod::Elemental;
  2         982808  
  2         35  
14 2     2   777 use Pod::Elemental::Transformer::Pod5;
  2         4  
  2         46  
15              
16 2     2   8 use Moose;
  2         4  
  2         14  
17 2     2   12172 use namespace::autoclean;
  2         5  
  2         17  
18              
19              
20             has cache => (
21             is => 'rw',
22             isa => 'HashRef[HashRef]',
23             builder => 'init_cache',
24             );
25              
26              
27             has maps => (
28             is => 'rw',
29             isa => 'HashRef[Str]',
30             lazy => 1,
31             builder => 'init_maps',
32             );
33              
34              
35             has callerPlugin => (
36             is => 'ro',
37             isa => 'Pod::Weaver::Plugin::Include',
38             );
39              
40              
41             has pod_path => (
42             is => 'rw',
43             lazy => 1,
44             isa => 'ArrayRef[Str]',
45             builder => 'init_pod_path',
46             );
47              
48             has logger => (
49             is => 'rw',
50             lazy => 1,
51             builder => 'init_logger',
52             handles => [qw<log log_debug log_fatal>],
53             );
54              
55              
56             has _tmplSource => (
57             is => 'rw',
58             clearer => '_clear_tmplSource',
59             isa => 'Str',
60             );
61              
62             has _tmplName => (
63             is => 'rw',
64             clearer => '_clear_tmplName',
65             isa => 'Str',
66             );
67              
68             has _tmplContent => (
69             is => 'rw',
70             isa => 'ArrayRef',
71             clearer => '_clear_tmplContent',
72             lazy => 1,
73             default => sub { [] },
74             );
75              
76              
77             sub find_source {
78 14     14 1 1546 my $this = shift;
79 14         34 my ($source) = @_;
80              
81 14         497 my $podFile = pod_where( { -dirs => $this->pod_path }, $source );
82              
83 14 50       84 return undef unless defined $podFile;
84              
85 14         577 $this->maps->{$source} = $podFile;
86              
87 14         52 return $podFile;
88             }
89              
90              
91             sub register_alias {
92 3     3 1 10 my $this = shift;
93 3         28 my ( $alias, $source ) = @_;
94              
95 3         15 my $podFile = $this->find_source($source);
96              
97 3 50       14 if ( defined $podFile ) {
98 3         127 $this->maps->{$alias} = $podFile;
99             }
100              
101 3         21 return $podFile;
102             }
103              
104              
105             sub _store_template {
106 39     39   69 my $this = shift;
107              
108 39 100       1157 return unless defined $this->_tmplName;
109            
110 24         614 $this->log_debug("Caching template", $this->_tmplName);
111              
112 24         2499 $this->cache->{ $this->_tmplSource }{ $this->_tmplName } =
113             $this->_tmplContent;
114              
115 24         789 $this->_clear_tmplName;
116 24         742 $this->_clear_tmplContent;
117             }
118              
119             sub _add2tmpl {
120 50     50   83 my $this = shift;
121 50         72 my $para = shift;
122            
123 50 100       1418 if ($this->_tmplName) {
124 49         74 push @{$this->_tmplContent}, $para;
  49         1507  
125             }
126             }
127              
128              
129             sub parse_tmpl {
130 36     36 1 344 my $this = shift;
131 36         62 my $str = shift;
132              
133 36         70 my $attrs = {};
134              
135 36 100       96 if ($str) {
136 32         180 $str =~ m/
137             ^\s*
138             (?<hidden>-)?
139             (?<name>
140             [\p{XPosixAlpha}_]
141             ([\p{XPosixAlnum}_])*
142             )
143             \s*$
144             ## Please see file perltidy.ERR
145             /xn;
146              
147 32 50   1   312 if ( $+{name} ) {
  1         12839  
  1         332  
  1         608  
148 32         176 $attrs->{name} = $+{name};
149 32         171 $attrs->{hidden} = defined $+{hidden};
150             }
151             else {
152             # $str is not empty but no valid name found.
153 0         0 $attrs->{badName} = 1;
154             }
155             }
156              
157 36         114 return $attrs;
158             }
159              
160              
161             sub load_file {
162 12     12 1 30 my $this = shift;
163 12         51 my ( $file, %opts ) = @_;
164              
165 12         70 $this->log_debug( "Loading file " . $file );
166              
167 12         622 my $showContent = 0;
168              
169 12         133 my $doc = Pod::Elemental->read_file($file);
170 12 50       76643 if ($doc) {
171 12         517 Pod::Elemental::Transformer::Pod5->new->transform_node($doc);
172              
173 12         75533 $this->_tmplSource($file);
174              
175 12         592 my $children = $doc->children;
176 12         135 ELEM: for ( my $i = 0 ; $i < @$children ; $i++ ) {
177 92         174 my $para = $children->[$i];
178 92 100       1725 if ( $para->isa('Pod::Elemental::Element::Pod5::Command') ) {
    100          
179 43 100       1231 if ( $para->command eq 'tmpl' ) {
180 27         322 $this->log_debug("Closing template by =tmpl");
181 27         2760 $this->_store_template;
182              
183 27         850 my $attrs = $this->parse_tmpl( $para->content );
184 27 100       93 $showContent = $attrs->{name} eq 'test' if $attrs->{name};
185 27 100       793 $this->_tmplName( $attrs->{name} ) if $attrs->{name};
186             }
187             else {
188 16         151 $this->_add2tmpl($para);
189             }
190 43         165 next ELEM;
191             }
192             elsif ( defined $this->_tmplName ) {
193 34         95 $this->_add2tmpl($para);
194             }
195             }
196              
197             # If any template was declared at the document end.
198 12         44 $this->log_debug("Closing any remaining template");
199 12         522 $this->_store_template;
200 12         413 $this->_clear_tmplSource;
201             }
202             else {
203 0         0 die "Failed to load doc from $file";
204             }
205              
206 12         362 return defined $doc;
207             }
208              
209              
210             sub get_template {
211 14     14 1 525 my $this = shift;
212 14         66 my %opts = @_;
213              
214 14         470 my $fullName = $this->maps->{ $opts{source} };
215              
216 14         31 my $template;
217              
218 14 100       38 unless ( defined $fullName ) {
219              
220             # Find file if specified by short name or module name.
221 8         41 $fullName = $this->find_source( $opts{source} );
222             }
223              
224 14 50       41 $this->log( "Cannot find source file for [" . $opts{source} . "]" )
225             unless defined $fullName;
226              
227 14 50       41 return undef unless defined $fullName;
228              
229             $this->log_debug(
230 14         113 "Found file $fullName for source [" . $opts{source} . "]" );
231              
232 14 100       1214 unless ( $template = $this->cache->{$fullName}{ $opts{template} } ) {
233 12 50       67 if ( my $doc = $this->load_file( $fullName, %opts ) ) {
234              
235 12         1666 $template = $this->cache->{$fullName}{ $opts{template} };
236             }
237             }
238 14         83 return $template;
239             }
240              
241             sub init_cache {
242 7     7 0 249 return {};
243             }
244              
245             sub init_maps {
246 5     5 0 205 return {};
247             }
248              
249             sub init_pod_path {
250 4     4 0 10 my $this = shift;
251              
252 4 50       171 return defined $this->callerPlugin
253             ? $this->callerPlugin->pod_path
254             : [qw<./lib>];
255             }
256              
257             sub init_logger {
258 5     5 0 17 my $this = shift;
259              
260 5         13 my $logger;
261              
262 5 100       208 if ( defined $this->callerPlugin ) {
263 4         161 $logger = $this->callerPlugin->logger;
264             }
265             else {
266 1         528 require Log::Dispatchouli;
267 1         178916 $logger = Log::Dispatchouli->new(
268             {
269             ident => '-Include::Finder',
270             to_stdout => 1,
271             log_pid => 0,
272             debug => 1,
273             }
274             );
275             }
276 5         56876 return $logger;
277             }
278              
279             __PACKAGE__->meta->make_immutable;
280 2     2   2558 no Moose;
  2         6  
  2         18  
281              
282             1;
283              
284             __END__
285              
286             =pod
287              
288             =encoding UTF-8
289              
290             =head1 NAME
291              
292             Pod::Weaver::Plugin::Include::Finder - Finds source Pods in .pod files or modules.
293              
294             =head1 VERSION
295              
296             version v0.1.5
297              
298             =head1 SYNOPSIS
299              
300             use Pod::Weaver::Plugin::Include::Finder;
301            
302             my $finder = Pod::Weaver::Plugin::Include::Finder->new;
303             my $template = $finder->get_template(
304             template => 'tmplName',
305             source => 'source.pod',
306             );
307              
308             =head1 DESCRIPTION
309              
310             This module loads sources, parses them and caches templates found.
311              
312             =head1 ATTRIBUTES
313              
314             =head2 B<cache>
315              
316             Cache of templates by sources. Hash of hashes where first level keys are
317             sources by their full file names; and second level keys are template names.
318             Each cache entry is an array of Pod nodes.
319              
320             =head2 B<maps>
321              
322             Mapping of short names into full path names. Short names are either aliases
323             or what is used with a C<=include> command. For example:
324              
325             =srcAlias alias Some::Module
326             =include template@templates/src.pod
327              
328             With these commands the map will contain keys I<alias> and I<templates/src.pod>.
329              
330             =head2 callerPlugin
331              
332             Back reference to a L<Pod::Weaver::Plugin::Include> instance.
333              
334             =head2 pod_path
335              
336             List of entries from C<pod_path> configuration variable.
337              
338             =head1 METHODS
339              
340             =head2 B<find_source( $source )>
341              
342             Takes a short source name (not alias!) and returns full path name for it or
343             I<undef> if not found.
344              
345             Successful search is stored into C<maps> attribute.
346              
347             =head2 B<register_alias( $alias, $source )>
348              
349             Finds out the full path name for C<$source> and stores a new entry for C<$alias>
350             in C<maps> attribute. Does nothing if source is not found.
351              
352             B<NOTE:> This method will result in two C<maps> entries: one for the C<$source>
353             and one for the C<$alias>.
354              
355             Returns full path name of the C<$source>.
356              
357             =head2 B<parse_tmpl( $str )>
358              
359             Parses argument of C<=tmpl> command. Returns a profile hash with two keys:
360              
361             =over 4
362              
363             =item C<hidden>
364              
365             Boolean, I<true> if template is declared hidden.
366              
367             =item C<name>
368              
369             Template name.
370              
371             =back
372              
373             =head2 C<load_file( $file )>
374              
375             Loads and parses a source file defined by C<$file>. The result is stored into
376             C<cache>.
377              
378             Returns I<true> if file has been successully read by L<Pod::Elemental>.
379              
380             =head2 C<get_template( %opts )>
381              
382             Returns a cached template. C<%opts> profile can have two keys:
383              
384             =over 4
385              
386             =item C<template>
387              
388             Template name
389              
390             =item C<source>
391              
392             Source in short form including aliases.
393              
394             =back
395              
396             If a template is missing in the C<cache> then tries to C<load_file()>.
397              
398             Returns I<undef> if failed.
399              
400             =head1 PRIVATE ATTRIBUTES
401              
402             =head2 B<_tmplSource, _tmplName, _tmplContent>
403              
404             Solely for use by C<load_file()> and C<_store_template()> methods.
405              
406             =head1 PRIVATE METHODS
407              
408             =head2 _store_template
409              
410             Records a new template into the C<cache>.
411              
412             =head1 AUTHOR
413              
414             Vadim Belman <vrurg@cpan.org>
415              
416             =head1 COPYRIGHT AND LICENSE
417              
418             This software is Copyright (c) 2017 by Vadim Belman.
419              
420             This is free software, licensed under:
421              
422             The (three-clause) BSD License
423              
424             =cut