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 )