App-jupiter
view release on metacpan or search on metacpan
script/jupiter view on Meta::CPAN
sub apply_template {
my $mnt = Mojo::Template->new;
return $mnt->render(@_);
}
=head1 TEMPLATES
The page template is called with three hash references: C<globals>, C<feeds>,
and C<entries>. The keys of these three hash references are documented below.
The values of these hashes are all I<escaped HTML> except where noted (dates and
file names, for example).
The technical details of how to write the templates are documented in the man
page for L<Mojo::Template>.
=head2 Globals
There are not many global keys.
B<date> is the the publication date of the HTML page, in ISO date format:
YYYY-MM-DD.
B<files> is the list of OPML files used.
=cut
sub globals {
my $files = shift;
my @time = gmtime;
my $today = DateTime->now->ymd;
return {date => $today, files => $files};
}
=head2 Writing templates for feeds
Feeds have the following keys available:
B<title> is the title of the feed.
B<url> is the URL of the feed (RSS or Atom). This is not the link to the site!
B<link> is the URL of the web page (HTML). This is the link to the site.
B<opml_file> is the file name where this feed is listed.
B<cache_dir> is the directory where this feed is cached.
B<message> is the HTTP status message or other warning or error that we got
while fetching the feed.
B<code> is the HTTP status code we got while fetching the feed.
B<doc> is the L<XML::LibXML::Document>. Could be either Atom or RSS!
=cut
# Creates list of feeds. Each feed is a hash with keys title, url, opml_file,
# cache_dir and cache_file.
sub read_opml {
my (@feeds, @files);
my @filters = map { decode(locale => substr($_, 1, -1)) } grep /^\/.*\/$/, @_;
for my $file (grep /\.opml$/, @_) {
my $doc = XML::LibXML->load_xml(location => $file); # this better have no errors!
my @nodes = $doc->findnodes('//outline[./@xmlUrl]');
my ($name, $path) = fileparse($file, '.opml', '.xml');
for my $node (@nodes) {
my $title = xml_escape $node->getAttribute('title');
my $url = xml_escape $node->getAttribute('xmlUrl');
next if @filters > 0 and not grep { $url =~ /$_/ or $title =~ /$_/ } @filters;
my $link = xml_escape $node->getAttribute('htmlUrl');
push @feeds, {
title => $title, # title in the OPML file
url => $url, # feed URL in the OPML file
link => $link, # web URL in the OPML file
opml_file => $file,
cache_dir => "$path/$name",
cache_file => "$path/$name/" . slugify($url),
};
}
warn "No feeds found in the OPML file $file\n" unless @nodes;
push @files, { file => $file, path => $path, name => $name };
}
@feeds = shuffle @feeds;
return \@feeds, \@files;
}
sub entries {
my $feeds = shift;
my $limit = shift;
my $date = DateTime->now(time_zone => 'UTC')->subtract( days => 90 ); # compute once
my $now = DateTime->now(time_zone => 'UTC');
my @entries;
for my $feed (@$feeds) {
next unless -r $feed->{cache_file};
my $doc = eval { XML::LibXML->load_xml(recover => 2, location => $feed->{cache_file} )};
if (not $doc) {
$feed->{message} = xml_escape "Parsing error: $@";
$feed->{code} = 422; # unprocessable
next;
}
$feed->{doc} = $doc;
my @nodes = $xpc->findnodes("/rss/channel/item | /atom:feed/atom:entry", $doc);
if (not @nodes) {
$feed->{message} = "Empty feed";
$feed->{code} = 204; # no content
next;
}
# if this is an Atom feed, we need to sort the entries ourselves (older entries at the end)
my @candidates = map {
my $entry = {};
$entry->{element} = $_;
$entry->{id} = id($_);
$entry->{date} = updated($_) || $undefined_date;
$entry;
} @nodes;
@candidates = grep { DateTime->compare($_->{date}, $now) <= 0 } @candidates;
@candidates = unique(sort { DateTime->compare( $b->{date}, $a->{date} ) } @candidates);
@candidates = @candidates[0 .. min($#candidates, $limit - 1)];
# now that we have limited the candidates, let's add more metadata from the feed
for my $entry (@candidates) {
$entry->{feed} = $feed;
( run in 1.335 second using v1.01-cache-2.11-cpan-ceb78f64989 )