public-inbox.git  about / heads / tags
an "archives first" approach to mailing lists
blob ab0f2fcc647d778f1c89206df7f928d50bacbfd1 4254 bytes (raw)
$ git show HEAD:lib/PublicInbox/RepoAtom.pm	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
 
# Copyright (C) all contributors <meta@public-inbox.org>
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
#
# git log => Atom feed (cgit-compatible: $REPO/atom/[PATH]?h=$tip
package PublicInbox::RepoAtom;
use v5.12;
use parent qw(PublicInbox::GzipFilter);
use POSIX qw(strftime);
use URI::Escape qw(uri_escape);
use Scalar::Util ();
use PublicInbox::Hval qw(ascii_html utf8_maybe);

# git for-each-ref and log use different format fields :<
my $ATOM_FMT = '--pretty=tformat:'.join('%n',
				map { "%$_" } qw(H ct an ae at s b)).'%x00';

my $EACH_REF_FMT = '--format='.join(';', map { "\$r{'$_'}=%($_)" } qw(
	objectname refname:short creator contents:subject contents:body
	*subject *body)).'%00';

sub atom_ok { # parse_hdr for qspawn
	my ($r, $bref, $ctx) = @_;
	return [ 404, [], [ "Not Found\n"] ] if $r == 0;
	bless $ctx, __PACKAGE__;
	my $h = [ 'Content-Type' => 'application/atom+xml; charset=UTF-8' ];
	$ctx->{gz} = $ctx->can('gz_or_noop')->($h, $ctx->{env});
	my $title = ascii_html(delete $ctx->{-feed_title});
	my $desc = ascii_html($ctx->{git}->description);
	my $url = ascii_html($ctx->{git}->base_url($ctx->{env}));
	$ctx->{-base_url} = $url;
	$ctx->zmore(<<EOM);
<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>$title</title><subtitle>$desc</subtitle><link
rel="alternate" type="text/html" href="$url"/>
EOM
	[ 200, $h, $ctx ]; # [2] is qspawn.filter
}

# called by GzipFilter->close
sub zflush { $_[0]->SUPER::zflush('</feed>') }

# called by GzipFilter->write or GetlineResponse->getline
sub translate {
	my $self = shift;
	$_[0] // return zflush($self); # getline caller
	my @out;
	my $lbuf = delete($self->{lbuf}) // shift;
	$lbuf .= shift while @_;
	my $is_tag = $self->{-is_tag};
	my ($H, $ct, $an, $ae, $at, $s, $bdy);
	while ($lbuf =~ s/\A([^\0]+)\0\n//s) {
		utf8_maybe($bdy = $1);
		if ($is_tag) {
			my %r;
			eval "$bdy";
			for (qw(contents:subject contents:body)) {
				$r{$_} =~ /\S/ or delete($r{$_})
			}
			$H = $r{objectname};
			$s = $r{'contents:subject'} // $r{'*subject'};
			$bdy = $r{'contents:body'} // $r{'*body'};
			$s .= " ($r{'refname:short'})";
			$_ = ascii_html($_) for ($s, $bdy, $r{creator});
			($an, $ae, $at) = split(/\s*&[gl]t;\s*/, $r{creator});
			$at =~ s/ .*\z//; # no TZ
			$ct = $at = strftime('%Y-%m-%dT%H:%M:%SZ', gmtime($at));
		} else {
			$bdy = ascii_html($bdy);
			($H, $ct, $an, $ae, $at, $s, $bdy) =
							split(/\n/, $bdy, 7);
			$at = strftime('%Y-%m-%dT%H:%M:%SZ', gmtime($at));
			$ct = strftime('%Y-%m-%dT%H:%M:%SZ', gmtime($ct));
		}
		$bdy //= '';
		push @out, <<"";
<entry><title>$s</title><updated>$ct</updated><author><name>$an</name>
<email>$ae</email></author><published>$at</published><link
rel="alternate" type="text/html" href="$self->{-base_url}$H/s/"
/><id>$H</id>

		push @out, <<'', $bdy, '</pre></div></content>' if $bdy ne '';
<content type="xhtml"><div
xmlns="http://www.w3.org/1999/xhtml"><pre style="white-space:pre-wrap">

		push @out, '</entry>';
	}
	$self->{lbuf} = $lbuf;
	chomp @out;
	@out ? $self->SUPER::translate(@out) : ''; # not EOF, yet
}

# $REPO/tags.atom endpoint
sub srv_tags_atom {
	my ($ctx) = @_;
	my $max = 50; # TODO configurable
	my @cmd = ('git', "--git-dir=$ctx->{git}->{git_dir}",
			qw(for-each-ref --sort=-creatordate), "--count=$max",
			'--perl', $EACH_REF_FMT, 'refs/tags');
	$ctx->{-feed_title} = "$ctx->{git}->{nick} tags";
	my $qsp = PublicInbox::Qspawn->new(\@cmd);
	$ctx->{-is_tag} = 1;
	$qsp->psgi_yield($ctx->{env}, undef, \&atom_ok, $ctx);
}

sub srv_atom {
	my ($ctx, $path) = @_;
	return if index($path, '//') >= 0 || index($path, '/') == 0;
	my $max = 50; # TODO configurable
	my @cmd = ('git', "--git-dir=$ctx->{git}->{git_dir}",
			qw(log --no-notes --no-color --no-abbrev),
			$ATOM_FMT, "-$max");
	my $tip = $ctx->{qp}->{h}; # same as cgit
	$ctx->{-feed_title} = $ctx->{git}->{nick};
	$ctx->{-feed_title} .= " $path" if $path ne '';
	if (defined($tip)) {
		push @cmd, $tip;
		$ctx->{-feed_title} .= ", $tip";
	}
	# else: let git decide based on HEAD if $tip isn't defined
	push @cmd, '--';
	push @cmd, $path if $path ne '';
	my $qsp = PublicInbox::Qspawn->new(\@cmd, undef,
					{ quiet => 1, 2 => $ctx->{lh} });
	$qsp->psgi_yield($ctx->{env}, undef, \&atom_ok, $ctx);
}

1;

git clone https://public-inbox.org/public-inbox.git
git clone http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/public-inbox.git