File Coverage

blib/lib/Feed/Data.pm
Criterion Covered Total %
statement 79 104 75.9
branch 5 10 50.0
condition 1 8 12.5
subroutine 22 26 84.6
pod 4 4 100.0
total 111 152 73.0


line stmt bran cond sub pod time code
1             package Feed::Data;
2              
3 12     12   1229110 use Moo;
  12         121434  
  12         58  
4 12     12   24261 use MooX::HandlesVia;
  12         127847  
  12         77  
5 12     12   9857 use Types::Standard qw/Any Str ArrayRef HashRef Optional/;
  12         1203841  
  12         177  
6 12     12   17296 use Carp qw(carp croak);
  12         28  
  12         934  
7 12     12   5091 use Feed::Data::Parser;
  12         43  
  12         448  
8 12     12   5658 use Feed::Data::Stream;
  12         47  
  12         453  
9 12     12   5628 use Feed::Data::Object;
  12         36  
  12         421  
10 12     12   6940 use HTML::TableContent;
  12         1051093  
  12         504  
11 12     12   8523 use JSON;
  12         127272  
  12         88  
12 12     12   1887 use Compiled::Params::OO qw/cpo/;
  12         29  
  12         145  
13 12     12   7351 use XML::RSS::LibXML;
  12         833040  
  12         108  
14 12     12   13532 use Text::CSV_XS qw/csv/;
  12         144981  
  12         773  
15              
16 12     12   227 use 5.006;
  12         46  
17             our $VERSION = '0.05';
18              
19             our $validate;
20             BEGIN {
21             $validate = cpo(
22             write => [Any, Str, Optional->of(Str)],
23 0         0 render => [Any, Optional->of(Str), {default => sub { 'text' }}],
24 12     12   124 generate => [Any, Optional->of(Str), {default => sub { 'text' }}],
  0         0  
25             rss => [Any, Optional->of(Str)],
26             raw => [Any, Optional->of(Str)],
27             text => [Any, Optional->of(Str)],
28             json => [Any, Optional->of(Str)],
29             csv => [Any, Optional->of(Str)],
30             table => [Any, Optional->of(Str)],
31             convert_feed => [Any, Str, Str]
32             );
33             }
34              
35             has 'feed' => (
36             is => 'rw',
37             isa => ArrayRef
38             lazy => 1,
39             default => sub { [ ] },
40             handles_via => 'Array',
41             handles => {
42             all => 'elements',
43             count => 'count',
44             get => 'get',
45             pop => 'pop',
46             delete => 'delete',
47             insert => 'unshift',
48             is_empty => 'is_empty',
49             clear => 'clear',
50             }
51             );
52              
53             has title => (
54             is => 'rw',
55             isa => Str
56             );
57              
58             has description => (
59             is => 'rw',
60             isa => Str
61             );
62              
63             has link => (
64             is => 'rw',
65             isa => Str
66             );
67              
68             has rss_channel => (
69             is => 'rw',
70             isa => HashRef
71             default => sub { { } }
72             );
73              
74             sub parse {
75 12     12 1 409 my ($self, $stream) = @_;
76              
77 12 50       53 if (!$stream) {
78 0         0 croak "No stream was provided to parse().";
79             }
80              
81 12         136 my $parser = Feed::Data::Parser->new(
82             stream => Feed::Data::Stream->new(stream => $stream)->open_stream
83             )->parse;
84              
85 12         35234 my $parsed = $parser->parse;
86 12         552 my $feed = $parser->feed;
87 12 50       127 return carp 'parse failed' unless $feed;
88              
89 12 100       248 if ($self->count >= 1) {
90 1         60 $self->insert(@{ $parsed });
  1         21  
91             } else {
92 11         1015 $self->feed($parsed);
93             }
94              
95 12         697 return 1;
96             }
97              
98             sub write {
99 5     5 1 70 my ($self, $stream, $type) = $validate->write->(@_);
100 5   50     217 Feed::Data::Stream->new(stream => $stream)->write_file($self->render($type || 'text'));
101 5         64 return 1;
102             }
103              
104             sub render {
105 5     5 1 310 my ( $self, $format ) = $validate->render->(@_);
106 5         102 $format = '_' . $format;
107 5         62 return $self->$format('render');
108             }
109              
110             sub generate {
111 0     0 1 0 my ( $self, $format ) = $validate->generate->(@_);
112 0         0 $format = '_' . $format;
113 0         0 return $self->$format('generate');
114             }
115              
116             sub _rss {
117 0     0   0 my ($self, $type) = $validate->rss->(@_);
118 0         0 my $rss = XML::RSS::LibXML->new(version => '1.0');
119             $rss->channel(
120             title => $self->title || "Feed::Data",
121             link => $self->link || "Feed::Data",
122             description => $self->description || "Feed::Data",
123 0   0     0 %{$self->rss_channel}
  0   0     0  
      0        
124             );
125 0         0 my @render = $self->_convert_feed('generate', 'json');
126 0         0 for (@render) {
127             $rss->add_item(
128 0         0 %{$_}
  0         0  
129             );
130             }
131 0         0 return $rss->as_string;
132             }
133              
134             sub _raw {
135 0     0   0 my ( $self, $type ) = $validate->raw->(@_);
136 0         0 my @render = $self->_convert_feed($type, 'raw');
137 0 0       0 if ($type eq q{render}) {
138 0         0 return join "\n", @render;
139             } else {
140 0         0 return \@render;
141             }
142             }
143              
144             sub _text {
145 1     1   7 my ( $self, $type ) = $validate->text->(@_);
146 1         80 my @render = $self->_convert_feed($type, 'text');
147 1 50       5 if ($type eq q{render}) {
148 1         14 return join "\n", @render;
149             } else {
150 0         0 return \@render;
151             }
152             }
153              
154             sub _json {
155 1     1   8 my ( $self, $type ) = $validate->json->(@_);
156 1         16 my @render = $self->_convert_feed('generate', 'json');
157 1         25 my $json = JSON->new->allow_nonref;
158 1         37 return $json->pretty->encode( \@render );
159             }
160              
161             sub _table {
162 2     2   28 my ( $self, $type ) = $validate->table->(@_);
163 2         38 my @render = $self->_convert_feed('generate', 'json');
164 2         34 my $table = HTML::TableContent->new();
165 2         1193 $table->create_table({
166             aoh => \@render,
167             order => [
168             qw/author title description category comment content date image link permalink tagline/
169             ]
170             })->render;
171             }
172              
173             sub _styled_table {
174 0     0   0 my ( $self, $type ) = $validate->table->(@_);
175 0         0 my @render = $self->_convert_feed('generate', 'json');
176 0         0 my $table = HTML::TableContent->new();
177 0         0 $table->create_table({
178             aoh => \@render,
179             order => [
180             qw/author title description category comment content date image link permalink tagline/
181             ]
182             })->render . '<style>
183             table {
184             font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
185             border-collapse: collapse;
186             width: 100%;
187             }
188              
189             td, th {
190             word-break: break-word;
191             min-width: 100px;
192             border: 1px solid #ddd;
193             padding: 8px;
194             }
195              
196             tr:nth-child(even){background-color: #f2f2f2;}
197              
198             tr:hover {background-color: #ddd;}
199              
200             th {
201             padding-top: 12px;
202             padding-bottom: 12px;
203             text-align: left;
204             background-color: #4c65af;
205             color: white;
206             }
207             </style>'
208             }
209              
210             sub _csv {
211 1     1   7 my ( $self, $type ) = $validate->json->(@_);
212 1         19 my @render = $self->_convert_feed('generate', 'json');
213 1         3 my $string;
214 1         9 csv (in => \@render, out => \$string);
215 1         535 return $string;
216             }
217              
218             sub _convert_feed {
219 5     5   37 my ( $self, $type, $format ) = $validate->convert_feed->(@_);
220 5         76 my @render;
221 5         12 foreach my $object ( @{$self->feed} ) {
  5         104  
222 10         102 push @render, $object->$type($format);
223             }
224 5         26 return @render;
225             }
226              
227             __PACKAGE__->meta->make_immutable;
228              
229             1;
230              
231             __END__
232              
233             =head1 NAME
234              
235             Feed::Data - dynamic data feeds
236              
237             =head1 VERSION
238              
239             Version 0.05
240              
241             =head1 SYNOPSIS
242              
243             use Feed::Data;
244              
245             my $feed = Feed::Data->new();
246             $feed->parse( 'https://services.parliament.uk/calendar/lords_main_chamber.rss' );
247              
248             $feed->all;
249             $feed->count;
250             $feed->delete($index);
251             $feed->get($index);
252              
253             $feed->write( 'path/to/empty.rss', 'rss' );
254             my $feed_text = $feed->render('rss');
255              
256             foreach my $object ( $feed->all ) {
257             $object->render('text'); # text, html, xml..
258             $object->hash('text'); # text, html, xml...
259             $object->fields('title', 'description'); # returns title and description object
260             $object->edit(title => 'WoW', description => 'something amazing'); # sets
261            
262             $object->title->text;
263             $object->link->raw;
264             $object->description->text;
265             $object->image->raw;
266             $object->date->text;
267            
268             $entry->title->as_text;
269             }
270              
271             ...
272              
273             use Feed::Data;
274              
275             my $feed = Feed::Data->new();
276            
277             $feed->parse( 'https://services.parliament.uk/calendar/commons_main_chamber.rss' );
278              
279             my $string = $feed->render('styled_table');
280              
281             $feed->clear;
282              
283             $feed->parse($string);
284              
285             =head1 DESCRIPTION
286              
287             Feed::Data is a frontend for building dynamic data feeds.
288              
289             =cut
290              
291             =head1 Methods
292              
293             =cut
294              
295             =head2 parse
296              
297             Populates the feed Attribute, this is an Array of Feed::Data::Object 's
298              
299             You can currently build Feeds by parsing xml (RSS, ATOM), JSON, CSV, plain text using key values seperated by a colon HTML via Meta Tags (twitter, opengraph) or table markup.
300              
301             =cut
302              
303             =over
304              
305             =item URI
306              
307             # any rss/atom feed or a web page that contains og or twitter markup
308             $feed->parse( 'http://examples.com/feed.xml' );
309              
310             =item File
311              
312             $feed->parse( 'path/to/feed.json' );
313              
314             =item Raw
315              
316             $feed->parse( 'qq{<?xml version="1.0"><feed> .... </feed>} );
317              
318             =back
319              
320             =head2 all
321              
322             returns all elements in the current feed
323              
324             $feed->all
325              
326             =cut
327              
328             =head2 count
329              
330             returns the count of the current data feed
331              
332             $feed->count
333              
334             =cut
335              
336             =head2 get
337              
338             accepts an index and returns an Feed::Data::Object from feed by its Array index
339              
340             $feed->get($index)
341              
342             =cut
343              
344             =head2 pop
345              
346             pop the last Feed::Data::Object from the current feed
347              
348             $feed->pop;
349              
350             =cut
351              
352             =head2 delete
353              
354             accepts an index and deletes the relevant Feed::Data::Object based on its Array index
355              
356             $feed->delete($index);
357              
358             =cut
359              
360             =head2 insert
361              
362             insert an 'Feed::Data::Object' into the feed
363              
364             $feed->insert($record)
365              
366             =cut
367              
368             =head2 is_empty
369              
370             returns true if Feed::Data is empty.
371              
372             $feed->is_empty
373            
374             =cut
375              
376             =head2 clear
377              
378             clear the current feed.
379              
380             $feed->clear
381              
382             =cut
383              
384             =head2 title
385              
386             Set the title of the rss feed the default is Feed::Data.
387              
388             $feed->title('Custom Title');
389              
390             =head2 link
391              
392             Set the link of the rss feed the default is Feed::Data.
393              
394             $feed->link('https://custom.link');
395              
396             =head2 description
397              
398             Set the description of the rss feed the default is Feed::Data.
399              
400             $feed->description('Custom Description');
401              
402             =head2 rss_channel
403              
404             Pass additional arguments into the rss feed channel section. See XML::RSS for more information.
405              
406             $feed->rss_channel({
407             dc => {
408             date => '2000-01-01T07:00+00:00',
409             subject => "LNATION",
410             creator => 'email@lnation.org',
411             publisher => 'email@lnation.org',
412             rights => 'Copyright 2000, lnation.org',
413             },
414             syn => {
415             updatePeriod => "hourly",
416             updateFrequency => "1",
417             updateBase => "1901-01-01T00:00+00:00",
418             },
419             });
420              
421             =head2 render
422              
423             render the feed using the passed in format, defaults to text.
424            
425             # raw - as taken from the feed
426             # text - stripped to plain text
427             # json
428             # rss
429             # csv
430             # table
431             # styled_table
432              
433             $feed->render('raw');
434              
435             =cut
436              
437             =head2 generate
438              
439             returns the feed object as a Array of hashes but with the values rendered, key being the field. You can also pass in a format.
440            
441             $feed->hash('text');
442              
443             =cut
444              
445             =head2 write
446              
447             Writes the current stream to file.
448              
449             $feed->write($file_path, $type);
450              
451             =head1 AUTHOR
452              
453             lnation, C<< <email at lnation.org> >>
454              
455             =head1 BUGS
456              
457             Please report any bugs or feature requests to C<bug-feed-data at rt.cpan.org>, or through
458             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Feed-Data>. I will be notified, and then you'll
459             automatically be notified of progress on your bug as I make changes.
460              
461             =head1 SUPPORT
462              
463             You can find documentation for this module with the perldoc command.
464              
465              
466             You can also look for information at:
467              
468             =over 4
469              
470             =item * RT: CPAN's request tracker (report bugs here)
471              
472             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Feed-Data>
473              
474             =item * AnnoCPAN: Annotated CPAN documentation
475              
476             L<http://annocpan.org/dist/Feed-Data>
477              
478             =item * CPAN Ratings
479              
480             L<http://cpanratings.perl.org/d/Feed-Data>
481              
482             =item * Search CPAN
483              
484             L<http://search.cpan.org/dist/Feed-Data/>
485              
486             =back
487              
488             =head1 ACKNOWLEDGEMENTS
489              
490             =head1 LICENSE AND COPYRIGHT
491              
492             Copyright 2016 LNATION.
493              
494             This program is free software; you can redistribute it and/or modify it
495             under the terms of the the Artistic License (2.0). You may obtain a
496             copy of the full license at:
497              
498             L<http://www.perlfoundation.org/artistic_license_2_0>
499              
500             Any use, modification, and distribution of the Standard or Modified
501             Versions is governed by this Artistic License. By using, modifying or
502             distributing the Package, you accept this license. Do not use, modify,
503             or distribute the Package, if you do not accept this license.
504              
505             If your Modified Version has been derived from a Modified Version made
506             by someone other than you, you are nevertheless required to ensure that
507             your Modified Version complies with the requirements of this license.
508              
509             This license does not grant you the right to use any trademark, service
510             mark, tradename, or logo of the Copyright Holder.
511              
512             This license includes the non-exclusive, worldwide, free-of-charge
513             patent license to make, have made, use, offer to sell, sell, import and
514             otherwise transfer the Package with respect to any patent claims
515             licensable by the Copyright Holder that are necessarily infringed by the
516             Package. If you institute patent litigation (including a cross-claim or
517             counterclaim) against any party alleging that the Package constitutes
518             direct or contributory patent infringement, then this Artistic License
519             to you shall terminate on the date that such litigation is filed.
520              
521             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
522             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
523             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
524             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
525             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
526             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
527             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
528             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
529              
530              
531             =cut
532              
533             1; # End of Feed::Data