File Coverage

blib/lib/Data/Object/Data.pm
Criterion Covered Total %
statement 104 109 95.4
branch 27 40 67.5
condition 17 32 53.1
subroutine 16 16 100.0
pod 7 11 63.6
total 171 208 82.2


line stmt bran cond sub pod time code
1             package Data::Object::Data;
2              
3 1     1   31402 use 5.014;
  1         3  
4              
5 1     1   5 use strict;
  1         1  
  1         27  
6 1     1   6 use warnings;
  1         2  
  1         21  
7 1     1   4 use routines;
  1         8  
  1         6  
8              
9 1     1   1491 use Moo;
  1         2  
  1         4  
10              
11             require Carp;
12              
13             our $VERSION = '2.03'; # VERSION
14              
15             # BUILD
16              
17             has data => (
18             is => 'ro',
19             builder => 'new_data',
20             lazy => 1
21             );
22              
23 13     13 0 80 fun new_data($self) {
  13         14  
24 13         158 my $file = $self->file;
25 13         76 my $data = $self->parser($self->lines);
26              
27 13         30 return $data;
28             }
29              
30             has file => (
31             is => 'ro',
32             builder => 'new_file',
33             lazy => 1
34             );
35              
36 1     1 0 8 fun new_file($self) {
  1         1  
37 1 50       6 my $from = $self->from or return;
38 0         0 my $path = $from =~ s/::/\//gr;
39              
40 0         0 return $INC{"$path.pm"};
41             }
42              
43             has from => (
44             is => 'ro',
45             lazy => 1
46             );
47              
48             has string => (
49             is => 'ro',
50             lazy => 1
51             );
52              
53 13     13 0 131047 fun BUILD($self, $args) {
  13         18  
54 13         183 $self->file;
55 13         222 $self->data;
56              
57 13         256 return $self;
58             }
59              
60             # METHODS
61              
62 2     2 1 10 method content($name) {
  2         3  
  2         2  
63 2 50       6 my $item = $self->item($name) or return;
64 2         4 my $data = $item->{data};
65              
66 2         17 return $data;
67             }
68              
69 3     3 1 12 method contents($name, $seek) {
  3         7  
  3         3  
70 3 50       7 my $items = $self->list($name) or return;
71 3 50       7 @$items = grep { $_->{name} eq $seek } @$items if $seek;
  0         0  
72 3         4 my $data = [map { $_->{data} } @$items];
  5         10  
73              
74 3         28 return $data;
75             }
76              
77 3     3 1 6 method item($name) {
  3         4  
  3         5  
78 3         3 for my $item (@{$self->{data}}) {
  3         6  
79 3 50 33     24 return $item if !$item->{list} && $item->{name} eq $name;
80             }
81              
82 0         0 return;
83             }
84              
85 13     13 0 18 method lines() {
  13         13  
86 13         154 my $file = $self->file;
87              
88 13 100 50     296 return $self->string || '' if !$file || !-f $file;
      66        
89              
90 12 50       405 open my $fh, '<', $file or Carp::confess "$!: $file";
91 12         899 my $lines = join "\n", <$fh>;
92 12         174 close $fh;
93              
94 12         76 return $lines;
95             }
96              
97 5     5 1 8 method list($name) {
  5         8  
  5         6  
98 5 50       10 return if !$name;
99              
100 5         5 my @list;
101              
102 5         6 for my $item (@{$self->{data}}) {
  5         11  
103 9 50 33     33 push @list, $item if $item->{list} && $item->{list} eq $name;
104             }
105              
106 5         15 return [sort { $a->{index} <=> $b->{index} } @list];
  4         24  
107             }
108              
109 1     1 1 6 method list_item($list, $name) {
  1         3  
  1         2  
110 1 50       3 my $items = $self->list($list) or return;
111 1         2 my $data = [grep { $_->{name} eq $name } @$items];
  2         5  
112              
113 1         9 return $data;
114             }
115              
116 14     14 1 26 method parser($data) {
  14         28  
  14         17  
117 14         1888 $data =~ s/\n*$/\n/;
118              
119 14         561 my @chunks = split /^(?:@=|=)\s*(.+?)\s*\r?\n/m, $data;
120              
121 14         27 shift @chunks;
122              
123 14         29 my $items = [];
124              
125 14         44 while (my ($meta, $data) = splice @chunks, 0, 2) {
126 138 100 66     400 next unless $meta && $data;
127 89 100       124 next unless $meta ne 'cut';
128              
129 87         178 my @info = split /\s/, $meta, 2;
130 87 100       161 my ($list, $name) = @info == 2 ? @info : (undef, @info);
131              
132 87         147 $data =~ s/\n\+=/\n=/g; # auto-escape nested pod syntax
133 87         330 $data = [split /\n\n/, $data];
134              
135 87         225 my $item = { name => $name, data => $data, index => @$items + 1, list => $list };
136              
137 87         249 push @$items, $item;
138             }
139              
140 14         67 return $items;
141             }
142              
143 5     5 1 22 method pluck($type, $name) {
  5         10  
  5         5  
144 5 50       9 return if !$name;
145 5 50 66     21 return if !$type || ($type ne 'item' && $type ne 'list');
      33        
146              
147 5         7 my (@list, @copy);
148              
149 5         7 for my $item (@{$self->{data}}) {
  5         9  
150 7         8 my $matched = 0;
151              
152 7 50 66     25 $matched = 1 if $type eq 'list' && $item->{list} && $item->{list} eq $name;
      33        
153 7 100 66     22 $matched = 1 if $type eq 'item' && $item->{name} && $item->{name} eq $name;
      66        
154              
155 7 100       13 push @list, $item if $matched;
156 7 100       14 push @copy, $item if !$matched;
157             }
158              
159 5         12 $self->{data} = [sort { $a->{index} <=> $b->{index} } @copy];
  0         0  
160              
161 5 50       59 return $type eq 'name' ? $list[0] : [@list];
162             }
163              
164             1;
165              
166             =encoding utf8
167              
168             =head1 NAME
169              
170             Data::Object::Data
171              
172             =cut
173              
174             =head1 ABSTRACT
175              
176             Podish Parser for Perl 5
177              
178             =cut
179              
180             =head1 SYNOPSIS
181              
182             package main;
183              
184             use Data::Object::Data;
185              
186             my $data = Data::Object::Data->new(
187             file => 't/Data_Object_Data.t'
188             );
189              
190             =cut
191              
192             =head1 DESCRIPTION
193              
194             This package provides methods for parsing and extracting pod-like sections from
195             any file or package. The pod-like syntax allows for using these sections
196             anywhere in the source code and having Perl properly ignoring them.
197              
198             =cut
199              
200             =head1 SCENARIOS
201              
202             This package supports the following scenarios:
203              
204             =cut
205              
206             =head2 syntax
207              
208             # POD
209              
210             # =head1 NAME
211             #
212             # Example #1
213             #
214             # =cut
215             #
216             # =head1 NAME
217             #
218             # Example #2
219             #
220             # =cut
221              
222             # Podish Syntax
223              
224             # =name
225             #
226             # Example #1
227             #
228             # =cut
229             #
230             # =name
231             #
232             # Example #2
233             #
234             # =cut
235              
236             # Podish Syntax (Nested)
237              
238             # =name
239             #
240             # Example #1
241             #
242             # +=head1 WHY?
243             #
244             # blah blah blah
245             #
246             # +=cut
247             #
248             # More information on the same topic as was previously mentioned in the
249             # previous section demonstrating the topic as-is obvious from said section
250             # ...
251             #
252             # =cut
253              
254             # Alternate Podish Syntax
255              
256             # @=name
257             #
258             # Example #1
259             #
260             # @=cut
261             #
262             # @=name
263             #
264             # Example #2
265             #
266             # @=cut
267              
268             my $data = Data::Object::Data->new(
269             file => 't/examples/alternate.pod'
270             );
271              
272             $data->contents('name');
273              
274             # [['Example #1'], ['Example #2']]
275              
276             This package supports parsing standard POD and pod-like sections from any file
277             or package, anywhere in the document. Additionally, this package supports an
278             alternative POD definition syntax which helps differentiate between the
279             traditional POD usage and other usages.
280              
281             =cut
282              
283             =head1 ATTRIBUTES
284              
285             This package has the following attributes:
286              
287             =cut
288              
289             =head2 data
290              
291             data(Str)
292              
293             This attribute is read-only, accepts C<(Str)> values, and is optional.
294              
295             =cut
296              
297             =head2 file
298              
299             file(Str)
300              
301             This attribute is read-only, accepts C<(Str)> values, and is optional.
302              
303             =cut
304              
305             =head2 from
306              
307             from(Str)
308              
309             This attribute is read-only, accepts C<(Str)> values, and is optional.
310              
311             =cut
312              
313             =head1 METHODS
314              
315             This package implements the following methods:
316              
317             =cut
318              
319             =head2 content
320              
321             content(Str $name) : ArrayRef[Str]
322              
323             The content method the pod-like section where the name matches the given
324             string.
325              
326             =over 4
327              
328             =item content example #1
329              
330             # =name
331             #
332             # Example #1
333             #
334             # =cut
335             #
336             # =name
337             #
338             # Example #2
339             #
340             # =cut
341              
342             my $data = Data::Object::Data->new(
343             file => 't/examples/content.pod'
344             );
345              
346             $data->content('name');
347              
348             # ['Example #1']
349              
350             =back
351              
352             =over 4
353              
354             =item content example #2
355              
356             # =name
357             #
358             # Example #1
359             #
360             # +=head1 WHY?
361             #
362             # blah blah blah
363             #
364             # +=cut
365             #
366             # More information on the same topic as was previously mentioned in the
367             # previous section demonstrating the topic as-is obvious from said section
368             # ...
369             #
370             # =cut
371              
372             my $data = Data::Object::Data->new(
373             file => 't/examples/nested.pod'
374             );
375              
376             $data->content('name');
377              
378             # ['Example #1', '', '=head1 WHY?', ...]
379              
380             =back
381              
382             =cut
383              
384             =head2 contents
385              
386             contents(Str $list, Str $name) : ArrayRef[ArrayRef]
387              
388             The contents method returns all pod-like sections that start with the given
389             string, e.g. C<pod> matches C<=pod foo>. This method returns an arrayref of
390             data for the matched sections. Optionally, you can filter the results by name
391             by providing an additional argument.
392              
393             =over 4
394              
395             =item contents example #1
396              
397             # =name example-1
398             #
399             # Example #1
400             #
401             # =cut
402             #
403             # =name example-2
404             #
405             # Example #2
406             #
407             # =cut
408              
409             my $data = Data::Object::Data->new(
410             file => 't/examples/contents.pod'
411             );
412              
413             $data->contents('name');
414              
415             # [['Example #1'], ['Example #2']]
416              
417             =back
418              
419             =over 4
420              
421             =item contents example #2
422              
423             # =name example-1
424             #
425             # Example #1
426             #
427             # +=head1 WHY?
428             #
429             # blah blah blah
430             #
431             # +=cut
432             #
433             # ...
434             #
435             # =cut
436              
437             my $data = Data::Object::Data->new(
438             string => join "\n\n", (
439             '=name example-1',
440             '',
441             'Example #1',
442             '',
443             '+=head1 WHY?',
444             '',
445             'blah blah blah',
446             '',
447             '+=cut',
448             '',
449             'More information on the same topic as was previously mentioned in the',
450             '',
451             'previous section demonstrating the topic as-is obvious from said section',
452             '',
453             '...',
454             '',
455             '=cut'
456             )
457             );
458              
459             $data->contents('name');
460              
461             # [['Example #1', '', '=head1 WHY?', ...]]
462              
463             =back
464              
465             =cut
466              
467             =head2 item
468              
469             item(Str $name) : HashRef
470              
471             The item method returns metadata for the pod-like section that matches the
472             given string.
473              
474             =over 4
475              
476             =item item example #1
477              
478             # =name
479             #
480             # Example #1
481             #
482             # =cut
483             #
484             # =name
485             #
486             # Example #2
487             #
488             # =cut
489              
490             my $data = Data::Object::Data->new(
491             file => 't/examples/content.pod'
492             );
493              
494             $data->item('name');
495              
496             # {
497             # index => 1,
498             # data => ['Example #1'],
499             # list => undef,
500             # name => 'name'
501             # }
502              
503             =back
504              
505             =cut
506              
507             =head2 list
508              
509             list(Str $name) : ArrayRef
510              
511             The list method returns metadata for each pod-like section that matches the
512             given string.
513              
514             =over 4
515              
516             =item list example #1
517              
518             # =name example-1
519             #
520             # Example #1
521             #
522             # =cut
523             #
524             # =name example-2
525             #
526             # Example #2
527             #
528             # =cut
529              
530             my $data = Data::Object::Data->new(
531             file => 't/examples/contents.pod'
532             );
533              
534             $data->list('name');
535              
536             # [{
537             # index => 1,
538             # data => ['Example #1'],
539             # list => 'name',
540             # name => 'example-1'
541             # },
542             # {
543             # index => 2,
544             # data => ['Example #2'],
545             # list => 'name',
546             # name => 'example-2'
547             # }]
548              
549             =back
550              
551             =cut
552              
553             =head2 list_item
554              
555             list_item(Str $list, Str $item) : ArrayRef[HashRef]
556              
557             The list_item method returns metadata for the pod-like sections that matches
558             the given list name and argument.
559              
560             =over 4
561              
562             =item list_item example #1
563              
564             # =name example-1
565             #
566             # Example #1
567             #
568             # =cut
569             #
570             # =name example-2
571             #
572             # Example #2
573             #
574             # =cut
575              
576             my $data = Data::Object::Data->new(
577             file => 't/examples/contents.pod'
578             );
579              
580             $data->list_item('name', 'example-2');
581              
582             # [{
583             # index => 2,
584             # data => ['Example #2'],
585             # list => 'name',
586             # name => 'example-2'
587             # }]
588              
589             =back
590              
591             =cut
592              
593             =head2 parser
594              
595             parser(Str $string) : ArrayRef
596              
597             The parser method extracts pod-like sections from a given string and returns an
598             arrayref of metadata.
599              
600             =over 4
601              
602             =item parser example #1
603              
604             # given: synopsis
605              
606             $data->parser("=pod\n\nContent\n\n=cut");
607              
608             # [{
609             # index => 1,
610             # data => ['Content'],
611             # list => undef,
612             # name => 'pod'
613             # }]
614              
615             =back
616              
617             =cut
618              
619             =head2 pluck
620              
621             pluck(Str $type, Str $item) : ArrayRef[HashRef]
622              
623             The pluck method splices and returns metadata for the pod-like section that
624             matches the given list or item by name. Splicing means that the parsed dataset
625             will be reduced each time this method returns data, making this useful with
626             iterators and reducers.
627              
628             =over 4
629              
630             =item pluck example #1
631              
632             # =name example-1
633             #
634             # Example #1
635             #
636             # =cut
637             #
638             # =name example-2
639             #
640             # Example #2
641             #
642             # =cut
643              
644             my $data = Data::Object::Data->new(
645             file => 't/examples/contents.pod'
646             );
647              
648             $data->pluck('list', 'name');
649              
650             # [{
651             # index => 1,
652             # data => ['Example #1'],
653             # list => 'name',
654             # name => 'example-1'
655             # },{
656             # index => 2,
657             # data => ['Example #2'],
658             # list => 'name',
659             # name => 'example-2'
660             # }]
661              
662             =back
663              
664             =over 4
665              
666             =item pluck example #2
667              
668             # =name example-1
669             #
670             # Example #1
671             #
672             # =cut
673             #
674             # =name example-2
675             #
676             # Example #2
677             #
678             # =cut
679              
680             my $data = Data::Object::Data->new(
681             file => 't/examples/contents.pod'
682             );
683              
684             $data->pluck('item', 'example-1');
685              
686             # [{
687             # index => 1,
688             # data => ['Example #1'],
689             # list => 'name',
690             # name => 'example-1'
691             # }]
692              
693             $data->pluck('item', 'example-2');
694              
695             # [{
696             # index => 2,
697             # data => ['Example #2'],
698             # list => 'name',
699             # name => 'example-2'
700             # }]
701              
702             =back
703              
704             =over 4
705              
706             =item pluck example #3
707              
708             # =name example-1
709             #
710             # Example #1
711             #
712             # =cut
713             #
714             # =name example-2
715             #
716             # Example #2
717             #
718             # =cut
719              
720             my $data = Data::Object::Data->new(
721             file => 't/examples/contents.pod'
722             );
723              
724             $data->pluck('list', 'name');
725              
726             # [{
727             # index => 1,
728             # data => ['Example #1'],
729             # list => 'name',
730             # name => 'example-1'
731             # },{
732             # index => 2,
733             # data => ['Example #2'],
734             # list => 'name',
735             # name => 'example-2'
736             # }]
737              
738             $data->pluck('list', 'name');
739              
740             # []
741              
742             =back
743              
744             =cut
745              
746             =head1 AUTHOR
747              
748             Al Newkirk, C<awncorp@cpan.org>
749              
750             =head1 LICENSE
751              
752             Copyright (C) 2011-2019, Al Newkirk, et al.
753              
754             This is free software; you can redistribute it and/or modify it under the terms
755             of the The Apache License, Version 2.0, as elucidated in the L<"license
756             file"|https://github.com/iamalnewkirk/data-object-data/blob/master/LICENSE>.
757              
758             =head1 PROJECT
759              
760             L<Wiki|https://github.com/iamalnewkirk/data-object-data/wiki>
761              
762             L<Project|https://github.com/iamalnewkirk/data-object-data>
763              
764             L<Initiatives|https://github.com/iamalnewkirk/data-object-data/projects>
765              
766             L<Milestones|https://github.com/iamalnewkirk/data-object-data/milestones>
767              
768             L<Contributing|https://github.com/iamalnewkirk/data-object-data/blob/master/CONTRIBUTE.md>
769              
770             L<Issues|https://github.com/iamalnewkirk/data-object-data/issues>
771              
772             =cut