App-DocKnot
view release on metacpan or search on metacpan
lib/App/DocKnot/Spin/RSS.pm view on Meta::CPAN
# Generate RSS and thread from a feed description file.
#
# This module generates RSS feeds and thread indexes of newly-published pages
# or change notes for a web site maintained with App::DocKnot::Spin.
#
# SPDX-License-Identifier: MIT
##############################################################################
# Modules and declarations
##############################################################################
package App::DocKnot::Spin::RSS v8.0.1;
use 5.024;
use autodie;
use parent qw(App::DocKnot);
use warnings FATAL => 'utf8';
use App::DocKnot::Spin::Thread;
use App::DocKnot::Util qw(print_checked print_fh);
use Carp qw(croak);
use Date::Language ();
use Date::Parse qw(str2time);
use Path::Tiny qw(path);
use POSIX qw(strftime);
##############################################################################
# Utility functions
##############################################################################
# Escapes &, <, and > characters for HTML or XML output.
#
# $string - Input string
#
# Returns: Escaped string
sub _escape {
my ($string) = @_;
$string =~ s{ & }{&}xmsg;
$string =~ s{ < }{<}xmsg;
$string =~ s{ > }{>}xmsg;
return $string;
}
# List intersection.
#
# $one - First list
# $two - Second list
#
# Returns: Common elements of both lists as a list
sub _intersect {
my ($one, $two) = @_;
my %one = map { $_ => 1 } $one->@*;
return grep { $one{$_} } $two->@*;
}
# Construct an absolute URL from a relative URL and a base URL. This plays
# fairly fast and loose with schemes and the like, since we don't need to be
# precise for our purposes.
#
# $url - Relative URL
# $base - Base URL to which it is relative
#
# Returns: Absolute URL
sub _absolute_url {
my ($url, $base) = @_;
# If $url is already absolute, return it.
return $url if $url =~ m{ \A [[:lower:]]+ : }xms;
# If $url starts with /, take only the scheme and host from the base URL.
if ($url =~ m{ \A / }xms) {
$base =~ s{ \A ( [[:lower:]]+ :// [^/]+ ) .* }{$1}xms;
return $base . $url;
}
# Otherwise, strip the last component off the base URL, and then strip
# more trailing components off the base URL for every ../ element in the
# relative URL. Then glue them together. This does not deal with the
# case where there are more ../ elements than there are elements in the
# base URL.
$base =~ s{ [^/]+ \z }{}xms;
while ($url =~ s{ \A [.][.]/+ }{}xms) {
$base =~ s{ [^/]+ /+ \z }{}xms;
}
return $base . $url;
}
# Construct a relative URL from an absolute URL and a base URL. If there is
# no base URL or if the URLs cannot be made relative to each other, return the
# relative URL unchanged.
#
# $url - Absolute URL
# $base - URL to which it should be relative
#
# Returns: Relative URL
sub _relative_url {
lib/App/DocKnot/Spin/RSS.pm view on Meta::CPAN
if ($ebook) {
splice(@page, $ebook, 4);
}
# Done with line-by-lne processing. Glue everything together into one
# page and do a bunch more random HTML cleanup.
my $page = join(q{}, @page);
$page =~ s{ ^ \s* <table[^>]+> }{<table>}xmsg;
$page =~ s{ ^ \s* <tr }{ <tr}xmsg;
$page =~ s{ ^ \s* <td[^>]+> }{ <td>}xmsg;
$page =~ s{ </tr> </table> </div> }{</tr></table>}xms;
$page =~ s{ <div [ ] class="review">}{}xms;
$page =~ s{ <p [ ] class="rating">}{<p>}xms;
$page =~ s{
<span [ ] class="story"><span [ ] id="\S+">(.*?)</span></span>
}{<strong>$1</strong>}xmsg;
# Add the author and title to the top of the HTML because we stripped out
# the top-level heading where this would normally have been.
$page = "<p>Review: <cite>$title</cite>, $author</p>\n\n" . $page;
# Return the cleaned-up page.
return $page . "\n";
}
# Print out the RSS version of the changes information given. Lots of this is
# hard-coded. Use the date of the last change as <pubDate> and the current
# time as <lastBuildDate>; it's not completely clear to me that this is
# correct.
#
# $file - Path::Tiny path to the output file
# $base - Base Path::Tiny path for input files
# $metadata_ref - Hash of metadata for the RSS feed
# $entries_ref - Array of entries in the RSS feed
sub _rss_output {
my ($self, $file, $base, $metadata_ref, $entries_ref) = @_;
# Determine the current date and latest publication date of all of the
# entries, published in the obnoxious format used by RSS.
my $lang = Date::Language->new('English');
my $format = '%a, %d %b %Y %H:%M:%S %z';
my $now = $lang->strftime($format, [localtime()]);
my $latest = $now;
if ($entries_ref->@*) {
$latest = strftime($format, localtime($entries_ref->[0]{date}));
}
# Determine the URL of the RSS file we're generating, if possible.
my $url;
if ($metadata_ref->{'rss-base'}) {
my $name = $file->basename();
$url = $metadata_ref->{'rss-base'} . $name;
}
# Format the entries.
my @formatted_entries;
for my $entry_ref ($entries_ref->@*) {
my $date = $lang->strftime($format, [localtime($entry_ref->{date})]);
my $description;
if ($entry_ref->{description}) {
$description = _escape($entry_ref->{description});
$description =~ s{ ^ }{ }xmsg;
$description =~ s{ \A (\s*) }{$1<p>}xms;
$description =~ s{ \n* \z }{</p>\n}xms;
} elsif ($entry_ref->{journal}) {
my $path = path($entry_ref->{journal})->absolute($base);
$description = $self->_rss_journal($path);
} elsif ($entry_ref->{review}) {
my $path = path($entry_ref->{review})->absolute($base);
$description = $self->_rss_review($path);
}
# Make all relative URLs absolute.
$description =~ s{
( < (?:a [ ] href | img [ ] src) = \" )
(?!http:)
( [./\w] [^\"]+ ) \"
}{ $1 . _absolute_url($2, $entry_ref->{link}) . qq{\"} }xmsge;
# Convert this into an object suitable for the output template.
my $formatted_ref = {
date => $date,
description => $description,
guid => $entry_ref->{guid},
link => $entry_ref->{link},
title => $entry_ref->{title},
};
push(@formatted_entries, $formatted_ref);
}
# Generate the RSS output using the template.
my %vars = (
base => $metadata_ref->{base},
description => $metadata_ref->{description},
docknot_version => $App::DocKnot::VERSION,
entries => \@formatted_entries,
language => $metadata_ref->{language},
latest => $latest,
now => $now,
title => $metadata_ref->{title},
url => $url,
);
my $result;
$self->{template}->process($self->{templates}{rss}, \%vars, \$result)
or croak($self->{template}->error());
# Write the result to the output file.
$file->spew_utf8($result);
return;
}
##############################################################################
# Thread output
##############################################################################
# Print out the thread version of the recent changes list.
#
# $file - Path::Tiny output path
# $metadata_ref - RSS feed metadata
# $entries_ref - Entries
sub _thread_output {
( run in 1.534 second using v1.01-cache-2.11-cpan-5837b0d9d2c )