diff --git a/cpanfile b/cpanfile index 831841c2..779c7d21 100644 --- a/cpanfile +++ b/cpanfile @@ -4,6 +4,7 @@ requires 'Class::Tiny'; requires 'Date::Format'; requires 'Path::Tiny'; requires 'Text::MicroTemplate'; +requires 'XML::Atom'; requires 'Text::Xatena'; requires 'Text::Markdown'; diff --git a/lib/PerlUsersJP/Builder.pm b/lib/PerlUsersJP/Builder.pm index 6b09a9c0..d213f938 100644 --- a/lib/PerlUsersJP/Builder.pm +++ b/lib/PerlUsersJP/Builder.pm @@ -10,6 +10,10 @@ use Path::Tiny (); use Date::Format (); use Scalar::Util (); use Text::MicroTemplate; +use XML::Atom::Feed; +use XML::Atom::Entry; +use XML::Atom::Person; +use XML::Atom::Link; use Text::Xatena; use Text::Xatena::Inline; @@ -75,7 +79,7 @@ sub build_entries { $self->build_categories($src_list); $self->build_tags($src_list); #$self->build_sitemap(\@entries); - #$self->build_atom(\@entries); + $self->build_atom_feed($src_list); } @@ -126,6 +130,22 @@ sub build_entry { $self->diag("Created entry $dest\n"); } +# content/foo/bar.txt => /foo/bar +sub entry_url_path { + my ($self, $src) = @_; + + my $content_dir = $self->content_dir; + my $path = "$src"; + $path =~ s!$content_dir!!; + $path =~ s!\.([^.]+)$!!; + return $path; +} + +sub entry_url { + my ($self, $src) = @_; + return "https://perl-users.jp" . $self->entry_url_path($src); +} + sub entry_text { my ($self, $src) = @_; my $matter = $self->front_matter($src); @@ -273,14 +293,8 @@ sub build_tag { my $file = Path::Tiny::path($_); my $matter = $self->front_matter($file); my $title = $matter->title; + my $href = $self->entry_url_path($file); - # content/foo/bar.txt => /foo/bar - my $href = do { - my $content_dir = $self->content_dir; - my $path = $file =~ s!$content_dir!!r; - my $href = $path =~ s!\.([^.]+)$!!r; - $href; - }; { file => $file, matter => $matter, @@ -308,9 +322,71 @@ sub build_sitemap { ... # TODO } -sub build_atom { - my ($self, $entries) = @_; - ... # TODO +my $ATOM_FEED_COUNT = 10; +my $ATOM_DATE_FORMAT = '%Y-%m-%dT%H:%M:%S%z'; + +sub build_atom_feed { + my ($self, $src_list) = @_; + + my $feed = XML::Atom::Feed->new; + $feed->title('新着記事 - Perl Users JP'); + $feed->id('tag:perl-users.jp,2020:/feed'); + $feed->lang('ja-JP'); + + { # link alternate + my $link = XML::Atom::Link->new; + $link->type('text/html'); + $link->rel('alternate'); + $link->href('https://perl-users.jp'); + $feed->add_link($link); + } + + { # link self + my $link = XML::Atom::Link->new; + $link->type('application/atom+xml'); + $link->rel('self'); + $link->href('https://perl-users.jp/feed.atom'); + $feed->add_link($link); + } + + my @sorted = sort { $b->stat->mtime <=> $a->stat->mtime } $src_list->@*; + my @new_src_list = splice @sorted, 0, $ATOM_FEED_COUNT; + for my $src (@new_src_list) { + my $entry = XML::Atom::Entry->new; + + my $matter = $self->front_matter($src); + my $path = $self->entry_url_path($src); + + $entry->title($matter->title); + $entry->id("tag:perl-users.jp,2020:$path"); + $entry->updated(Date::Format::time2str($ATOM_DATE_FORMAT, $src->stat->mtime)); + #$entry->published(Date::Format::time2str($ATOM_DATE_FORMAT, $src->stat->mtime)); # FIXME mtime + $entry->content( + $self->entry_text($src) + ); + + my $author = XML::Atom::Person->new; + $author->name($matter->author); + $entry->author($author); + + my $link = XML::Atom::Link->new; + $link->type('text/html'); + $link->rel('alternate'); + $link->href($self->entry_url($src)); + + $feed->add_entry($entry); + } + + my $first_src = $new_src_list[0]; + $feed->updated(Date::Format::time2str($ATOM_DATE_FORMAT, $first_src->stat->mtime)); + + my $xml = $feed->as_xml; + + my $atom_dir = $self->public_dir->child(); + my $dest = $atom_dir->child('feed.atom'); + $atom_dir->mkpath unless $atom_dir->is_dir; + $dest->spew($xml); + $self->diag("Created atom $dest\n"); } sub diag { @@ -365,6 +441,9 @@ sub format_text { $parser->html_footer(''); $parser->parse_string_document("=pod\n\n$text"); } + elsif ($format eq 'html') { + return $text; + } else { die "unsupported format: $format"; } diff --git a/script/app.psgi b/script/app.psgi index bf5aa660..6860d2d0 100644 --- a/script/app.psgi +++ b/script/app.psgi @@ -6,5 +6,15 @@ builder { s!/([^\.]+)$!/$1/!; s!(.*/$)!$1/index.html! or return qr{^/.+}; }, + content_type => sub { + my $file = shift; + my $content_type = Plack::MIME->mime_type($file) || 'text/plain'; + + if ($content_type eq 'application/atom+xml') { + $content_type .= "; charset=utf-8"; + } + + return $content_type; + }, root => './public/'; };