+ my ($ctx) = @_;
+ sub { emit_atom($_[0], $ctx) };
+}
+
+sub generate_html_index {
+ my ($ctx) = @_;
+ sub { emit_html_index($_[0], $ctx) };
+}
+
+# private subs
+
+sub emit_atom {
+ my ($cb, $ctx) = @_;
+ require POSIX;
+ my $fh = $cb->([ 200, ['Content-Type' => 'application/xml']]);
+ my $max = $ctx->{max} || MAX_PER_PAGE;
+ my $feed_opts = get_feedopts($ctx);
+ my $addr = $feed_opts->{address};
+ $addr = $addr->[0] if ref($addr);
+ $addr ||= 'public-inbox@example.com';
+ my $title = $feed_opts->{description} || "unnamed feed";
+ $title = PublicInbox::Hval->new_oneline($title)->as_html;
+ my $type = index($title, '&') >= 0 ? "\ntype=\"html\"" : '';
+ my $url = $feed_opts->{url} || "http://example.com/";
+ my $atomurl = $feed_opts->{atomurl};
+ $fh->write(qq(<?xml version="1.0" encoding="us-ascii"?>\n) .
+ qq{<feed\nxmlns="http://www.w3.org/2005/Atom">} .
+ qq{<title$type>$title</title>} .
+ qq{<link\nhref="$url"/>} .
+ qq{<link\nrel="self"\nhref="$atomurl"/>} .
+ qq{<id>mailto:$addr</id>} .
+ '<updated>' . POSIX::strftime(DATEFMT, gmtime) . '</updated>');
+
+ my $git = PublicInbox::GitCatFile->new($ctx->{git_dir});
+ each_recent_blob($ctx, sub {
+ my ($add, undef) = @_;
+ add_to_feed($feed_opts, $fh, $add, $git);
+ });
+ $git = undef; # destroy pipes
+ Email::Address->purge_cache;
+ $fh->write("</feed>");
+ $fh->close;
+}
+
+
+sub emit_html_index {
+ my ($cb, $ctx) = @_;
+ my $fh = $cb->([200,['Content-Type'=>'text/html; charset=UTF-8']]);
+
+ my $max = $ctx->{max} || MAX_PER_PAGE;
+ my $feed_opts = get_feedopts($ctx);
+
+ my $title = $feed_opts->{description} || '';
+ $title = PublicInbox::Hval->new_oneline($title)->as_html;
+ my $atom_url = $feed_opts->{atomurl};
+
+ $fh->write("<html><head><title>$title</title>" .
+ "<link\nrel=alternate\ntitle=\"Atom feed\"\n".
+ "href=\"$atom_url\"\ntype=\"application/atom+xml\"/>" .
+ '</head><body>' . PublicInbox::View::PRE_WRAP .
+ "<b>$title</b> (<a\nhref=\"$atom_url\">Atom feed</a>)\n");
+
+ my $state;
+ my $git = PublicInbox::GitCatFile->new($ctx->{git_dir});
+ my $topics;
+ my $srch = $ctx->{srch};
+ $srch and $topics = [ [], {} ];
+ my (undef, $last) = each_recent_blob($ctx, sub {
+ my ($path, $commit, $ts, $u, $subj) = @_;
+ $state ||= [ undef, {}, $commit, 0 ];
+
+ if ($srch) {
+ add_topic($git, $srch, $topics, $path, $ts, $u, $subj);
+ } else {
+ my $mime = do_cat_mail($git, $path) or return 0;
+ PublicInbox::View::index_entry($fh, $mime, 0, $state);
+ 1;
+ }
+ });
+ Email::Address->purge_cache;
+ $git = undef; # destroy pipes.
+
+ my $footer = nav_footer($ctx->{cgi}, $last, $feed_opts, $state);
+ if ($footer) {
+ my $list_footer = $ctx->{footer};
+ $footer .= "\n" . $list_footer if $list_footer;
+ $footer = "<hr /><pre>$footer</pre>";
+ }
+ $fh->write(dump_topics($topics)) if $topics;
+ $fh->write("$footer</body></html>");
+ $fh->close;
+}
+
+sub nav_footer {
+ my ($cgi, $last, $feed_opts, $state) = @_;
+ $cgi or return '';
+ my $old_r = $cgi->param('r');
+ my $head = ' ';
+ my $next = ' ';
+ # $state = [ undef, {}, $first_commit, $last_anchor ];
+ my $first = $state->[2];
+ my $anchor = $state->[3];
+
+ if ($last) {
+ $next = qq!<a\nhref="?r=$last">next</a>!;
+ }
+ if ($old_r) {
+ $head = $cgi->path_info;
+ $head = qq!<a\nhref="$head">head</a>!;
+ }
+ my $atom = "<a\nhref=\"$feed_opts->{atomurl}\">atom</a>";
+ my $permalink = "<a\nhref=\"?r=$first\">permalink</a>";
+ "<a\nname=\"s$anchor\">page:</a> $next $head $atom $permalink";
+}
+
+sub each_recent_blob {
+ my ($ctx, $cb) = @_;
+ my $max = $ctx->{max} || MAX_PER_PAGE;
+ my $hex = '[a-f0-9]';
+ my $addmsg = qr!^:000000 100644 \S+ \S+ A\t(${hex}{2}/${hex}{38})$!;
+ my $delmsg = qr!^:100644 000000 \S+ \S+ D\t(${hex}{2}/${hex}{38})$!;
+ my $refhex = qr/${hex}{4,40}(?:~\d+)?/;
+ my $cgi = $ctx->{cgi};
+
+ # revision ranges may be specified
+ my $range = 'HEAD';
+ my $r = $cgi->param('r') if $cgi;
+ if ($r && ($r =~ /\A(?:$refhex\.\.)?$refhex\z/o)) {
+ $range = $r;
+ }