about summary refs log tree commit homepage
path: root/lib/PublicInbox/ViewDiff.pm
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2019-01-17 11:51:34 +0000
committerEric Wong <e@80x24.org>2019-01-19 03:34:54 +0000
commitca0a28d52d43609cd6cabf5af085260032e5afa9 (patch)
treeae4dd9024e4e5bd9316a2bd8dd5a92448317d7c7 /lib/PublicInbox/ViewDiff.pm
parent9b6294536b0d3f6781a6ceb0c4b9f173093816b5 (diff)
downloadpublic-inbox-ca0a28d52d43609cd6cabf5af085260032e5afa9.tar.gz
Diffstat (limited to 'lib/PublicInbox/ViewDiff.pm')
-rw-r--r--lib/PublicInbox/ViewDiff.pm147
1 files changed, 147 insertions, 0 deletions
diff --git a/lib/PublicInbox/ViewDiff.pm b/lib/PublicInbox/ViewDiff.pm
new file mode 100644
index 00000000..ee450fa7
--- /dev/null
+++ b/lib/PublicInbox/ViewDiff.pm
@@ -0,0 +1,147 @@
+# Copyright (C) 2019 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+#
+# used by PublicInbox::View
+package PublicInbox::ViewDiff;
+use strict;
+use warnings;
+use base qw(Exporter);
+our @EXPORT_OK = qw(flush_diff);
+
+use PublicInbox::Hval qw(ascii_html);
+use PublicInbox::Git qw(git_unquote);
+
+sub DSTATE_INIT () { 0 }
+sub DSTATE_STAT () { 1 } # TODO
+sub DSTATE_HEAD () { 2 } # /^diff --git /, /^index /, /^--- /, /^\+\+\+ /
+sub DSTATE_HUNK () { 3 } # /^@@ /
+sub DSTATE_CTX () { 4 } # /^ /
+sub DSTATE_ADD () { 5 } # /^\+/
+sub DSTATE_DEL () { 6 } # /^\-/
+
+my $OID_NULL = '0{7,40}';
+my $OID_BLOB = '[a-f0-9]{7,40}';
+my $PATH_A = '"?a/.+|/dev/null';
+my $PATH_B = '"?b/.+|/dev/null';
+
+sub to_html ($$) {
+        $_[0]->linkify_1($_[1]);
+        $_[0]->linkify_2(ascii_html($_[1]));
+}
+
+# link to line numbers in blobs
+sub diff_hunk ($$$$) {
+        my ($dctx, $spfx, $ca, $cb) = @_;
+        my $oid_a = $dctx->{oid_a};
+        my $oid_b = $dctx->{oid_b};
+
+        (defined($oid_a) && defined($oid_b)) or return "@@ $ca $cb @@";
+
+        my ($n) = ($ca =~ /^-(\d+)/);
+        $n = defined($n) ? do { ++$n; "#n$n" } : '';
+
+        my $rv = qq(@@ <a\nhref=$spfx$oid_a/s$n>$ca</a>);
+
+        ($n) = ($cb =~ /^\+(\d+)/);
+        $n = defined($n) ? do { ++$n; "#n$n" } : '';
+
+        $rv .= qq( <a\nhref=$spfx$oid_b/s$n>$cb</a> @@);
+}
+
+sub flush_diff ($$$$) {
+        my ($dst, $spfx, $linkify, $diff) = @_;
+        my $state = DSTATE_INIT;
+        my $dctx; # {}, keys: oid_a, oid_b, path_a, path_b
+
+        foreach my $s (@$diff) {
+                if ($s =~ /^ /) {
+                        if ($state == DSTATE_HUNK || $state == DSTATE_ADD ||
+                            $state == DSTATE_DEL || $state == DSTATE_HEAD) {
+                                $$dst .= "</span><span\nclass=ctx>";
+                                $state = DSTATE_CTX;
+                        }
+                        $$dst .= to_html($linkify, $s);
+                } elsif ($s =~ /^-- $/) { # email signature begins
+                        if ($state != DSTATE_INIT) {
+                                $state = DSTATE_INIT;
+                                $$dst .= '</span>';
+                        }
+                        $$dst .= $s;
+                } elsif ($s =~ m!^diff --git ($PATH_A) ($PATH_B)$!x) {
+                        if ($state != DSTATE_HEAD) {
+                                my ($pa, $pb) = ($1, $2);
+                                $$dst .= '</span>' if $state != DSTATE_INIT;
+                                $$dst .= "<span\nclass=head>";
+                                $state = DSTATE_HEAD;
+                                $pa = (split('/', git_unquote($pa), 2))[1];
+                                $pb = (split('/', git_unquote($pb), 2))[1];
+                                $dctx = { path_a => $pa, path_b => $pb };
+                        }
+                        $$dst .= to_html($linkify, $s);
+                } elsif ($s =~ s/^(index $OID_NULL\.\.)($OID_BLOB)\b//o) {
+                        $$dst .= qq($1<a\nhref=$spfx$2/s>$2</a>);
+                        $$dst .= to_html($linkify, $s) ;
+                } elsif ($s =~ s/^index ($OID_NULL)(\.\.$OID_BLOB)\b//o) {
+                        $$dst .= 'index ';
+                        $$dst .= qq(<a\nhref=$spfx$1/s>$1</a>$2);
+                        $$dst .= to_html($linkify, $s);
+                } elsif ($s =~ /^index ($OID_BLOB)\.\.($OID_BLOB)/o) {
+                        $dctx->{oid_a} = $1;
+                        $dctx->{oid_b} = $2;
+                        $$dst .= to_html($linkify, $s);
+                } elsif ($s =~ s/^@@ (\S+) (\S+) @@//) {
+                        my ($ca, $cb) = ($1, $2);
+                        if ($state == DSTATE_HEAD || $state == DSTATE_CTX ||
+                            $state == DSTATE_ADD || $state == DSTATE_DEL) {
+                                $$dst .= "</span><span\nclass=hunk>";
+                                $state = DSTATE_HUNK;
+                                $$dst .= diff_hunk($dctx, $spfx, $ca, $cb);
+                        } else {
+                                $$dst .= to_html($linkify, "@@ $ca $cb @@");
+                        }
+                        $$dst .= to_html($linkify, $s);
+                } elsif ($s =~ m!^--- $PATH_A!) {
+                        if ($state == DSTATE_INIT) { # color only (no oid link)
+                                $state = DSTATE_HEAD;
+                                $$dst .= "<span\nclass=head>";
+                        }
+                        $$dst .= to_html($linkify, $s);
+                } elsif ($s =~ m!^\+{3} $PATH_B!)  {
+                        if ($state == DSTATE_INIT) { # color only (no oid link)
+                                $state = DSTATE_HEAD;
+                                $$dst .= "<span\nclass=head>";
+                        }
+                        $$dst .= to_html($linkify, $s);
+                } elsif ($s =~ /^\+/) {
+                        if ($state != DSTATE_ADD && $state != DSTATE_INIT) {
+                                $$dst .= "</span><span\nclass=add>";
+                                $state = DSTATE_ADD;
+                        }
+                        $$dst .= to_html($linkify, $s);
+                } elsif ($s =~ /^-/) {
+                        if ($state != DSTATE_DEL && $state != DSTATE_INIT) {
+                                $$dst .= "</span><span\nclass=del>";
+                                $state = DSTATE_DEL;
+                        }
+                        $$dst .= to_html($linkify, $s);
+                # ignore the following lines in headers:
+                } elsif ($s =~ /^(?:dis)similarity index/ ||
+                         $s =~ /^(?:old|new) mode/ ||
+                         $s =~ /^(?:deleted|new) file mode/ ||
+                         $s =~ /^(?:copy|rename) (?:from|to) / ||
+                         $s =~ /^(?:dis)?similarity index /) {
+                        $$dst .= to_html($linkify, $s);
+                } else {
+                        if ($state != DSTATE_INIT) {
+                                $$dst .= '</span>';
+                                $state = DSTATE_INIT;
+                        }
+                        $$dst .= to_html($linkify, $s);
+                }
+        }
+        @$diff = ();
+        $$dst .= '</span>' if $state != DSTATE_INIT;
+        undef;
+}
+
+1;