App-BookmarkFeed
view release on metacpan or search on metacpan
lib/App/BookmarkFeed.pm view on Meta::CPAN
my $db_file = File::Spec->catfile($dirs, $filename . ".db");
my $sql = Mojo::SQLite->new("sqlite:$db_file");
$sql->migrations->from_data->migrate;
my $db = $sql->db;
for (@files) { die "$_ is not readable\n" unless -r $_ }
my @items;
for my $file (@files) {
my $dt = DateTime->from_epoch(epoch => (stat($file))[9]);
my $md = read_text($file);
my $parser = CommonMark::Parser->new;
$parser->feed($md);
my $doc = $parser->finish;
push(@items, to_items($doc, $dt));
}
update($db, @items);
write_feed($db, $feed_file);
}
sub to_items ($doc, $dt) {
my $iter = $doc->iterator;
my @items;
while (my ($ev_type, $node) = $iter->next) {
if ($node->get_type == NODE_LINK) {
if ($ev_type == EVENT_ENTER) {
if ($node->get_url =~ /^(https?|gemini|gopher):/) {
my $last = $node->parent->last_child;
# links in a paragraph that end in a colon are skipped
next if $last->get_type == NODE_TEXT and $last->get_literal =~ /:$/;
push(@items, to_item($node, $dt));
}
}
}
}
return @items;
}
# An item is a hash with keys url, title, description; url and title are plain
# text, description is HTML.
sub to_item ($node, $dt) {
my $item = {
url => $node->get_url,
date => $dt,
};
my $child = $node->first_child;
if ($child && $child->get_type == NODE_TEXT) {
$item->{title} = $child->get_literal;
}
while ($node = $node->parent) {
my $node_type = $node->get_type;
if ($node_type == NODE_PARAGRAPH
|| $node_type == NODE_BLOCK_QUOTE
|| $node_type == NODE_ITEM) {
my $html = $node->render_html;
$item->{description} = $html;
last;
}
}
return $item
}
sub update ($db, @items) {
for my $item (@items) {
if ($db->query('select 1 from items where url = ?', $item->{url})->hash) {
delete $item->{date};
$db->update('items', $item, {url => $item->{url}});
} else {
$db->insert('items', $item);
}
}
}
sub write_feed ($db, $feed_file) {
my $mt = Mojo::Template->new(vars => 1);
my $items = $db->select('items',
[qw(date url title description)],
{ },
{
order_by => {-desc => 'date'},
limit => 40,
})->hashes;
$_->{date} = DateTime::Format::ISO8601
->parse_datetime($_->{date})
->strftime("%a, %d %b %Y %H:%M:%S %z")
for @$items;
my $feed = $mt->render(<<'EOT', { items => $items});
<rss version="2.0">
<channel>
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
<title>Bookmarks</title>
% for my $item (@$items) {
<item>
<title><%== $item->{title} %></title>
<link><%= $item->{url} %></link>
<guid><%= $item->{url} %></guid>
<description><%== $item->{description} %></description>
<pubDate><%= $item->{date} %></pubDate>
</item>
% }
</channel>
</rss>
EOT
write_text($feed_file, $feed);
}
1;
__DATA__
@@ migrations
-- 1 up
create table items (date date, url text, title text, description text);
-- 1 down
drop table urls;
( run in 1.012 second using v1.01-cache-2.11-cpan-524268b4103 )