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{ & }{&amp;}xmsg;
    $string =~ s{ < }{&lt;}xmsg;
    $string =~ s{ > }{&gt;}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 )