blob: 018268a92a43638dd458626bd5370b2707d7b84f [file] [log] [blame]
#!/usr/bin/perl -w
$SIG{'PIPE'} = 'IGNORE';
my ($old, $new);
if (@ARGV == 7) {
# called as GIT_EXTERNAL_DIFF script
$old = parse_cooking($ARGV[1]);
$new = parse_cooking($ARGV[4]);
} else {
# called with old and new
$old = parse_cooking($ARGV[0]);
$new = parse_cooking($ARGV[1]);
}
compare_cooking($old, $new);
################################################################
use File::Temp qw(tempfile);
sub compare_them {
local($_);
my ($a, $b, $force, $soft) = @_;
if ($soft) {
$plus = $minus = ' ';
} else {
$plus = '+';
$minus = '-';
}
if (!defined $a->[0]) {
return map { "$plus$_\n" } map { split(/\n/) } @{$b};
} elsif (!defined $b->[0]) {
return map { "$minus$_\n" } map { split(/\n/) } @{$a};
} elsif (join('', @$a) eq join('', @$b)) {
if ($force) {
return map { " $_\n" } map { split(/\n/) } @{$a};
} else {
return ();
}
}
my ($ah, $aname) = tempfile();
my ($bh, $bname) = tempfile();
my $cnt = 0;
my @result = ();
for (@$a) {
print $ah $_;
$cnt += tr/\n/\n/;
}
for (@$b) {
print $bh $_;
$cnt += tr/\n/\n/;
}
close $ah;
close $bh;
open(my $fh, "-|", 'diff', "-U$cnt", $aname, $bname);
$cnt = 0;
while (<$fh>) {
next if ($cnt++ < 3);
push @result, $_;
}
close $fh;
unlink ($aname, $bname);
return @result;
}
sub flush_topic {
my ($cooking, $name, $desc) = @_;
my $section = $cooking->{SECTIONS}[-1];
return if (!defined $name);
$desc =~ s/\s+\Z/\n/s;
$desc =~ s/\A\s+//s;
my $topic = +{
IN_SECTION => $section,
NAME => $name,
DESC => $desc,
};
$cooking->{TOPICS}{$name} = $topic;
push @{$cooking->{TOPIC_ORDER}}, $name;
}
sub parse_section {
my ($cooking, @line) = @_;
while (@line && $line[-1] =~ /^\s*$/) {
pop @line;
}
return if (!@line);
if (!exists $cooking->{SECTIONS}) {
$cooking->{SECTIONS} = [];
$cooking->{TOPICS} = {};
$cooking->{TOPIC_ORDER} = [];
}
if (!exists $cooking->{HEADER}) {
my $line = join('', @line);
$line =~ s/\A.*?\n\n//s;
$cooking->{HEADER} = $line;
return;
}
if (!exists $cooking->{GREETING}) {
$cooking->{GREETING} = join('', @line);
return;
}
my ($section_name, $topic_name, $topic_desc);
for (@line) {
if (!defined $section_name && /^\[(.*)\]$/) {
$section_name = $1;
push @{$cooking->{SECTIONS}}, $section_name;
next;
}
if (/^\* (\S+) /) {
my $next_name = $1;
flush_topic($cooking, $topic_name, $topic_desc);
$topic_name = $next_name;
$topic_desc = '';
}
$topic_desc .= $_;
}
flush_topic($cooking, $topic_name, $topic_desc);
}
sub dump_cooking {
my ($cooking) = @_;
print $cooking->{HEADER};
print "-" x 50, "\n";
print $cooking->{GREETING};
for my $section_name (@{$cooking->{SECTIONS}}) {
print "\n", "-" x 50, "\n";
print "[$section_name]\n";
for my $topic_name (@{$cooking->{TOPIC_ORDER}}) {
$topic = $cooking->{TOPICS}{$topic_name};
next if ($topic->{IN_SECTION} ne $section_name);
print "\n", $topic->{DESC};
}
}
}
sub parse_cooking {
my ($filename) = @_;
my (%cooking, @current, $fh);
open $fh, "<", $filename
or die "cannot open $filename: $!";
while (<$fh>) {
if (/^-{30,}$/) {
parse_section(\%cooking, @current);
@current = ();
next;
}
push @current, $_;
}
close $fh;
parse_section(\%cooking, @current);
return \%cooking;
}
sub compare_topics {
my ($a, $b) = @_;
if (!@$a || !@$b) {
print compare_them($a, $b, 1, 1);
return;
}
# otherwise they both have title.
$a = [map { "$_\n" } split(/\n/, join('', @$a))];
$b = [map { "$_\n" } split(/\n/, join('', @$b))];
my $atitle = shift @$a;
my $btitle = shift @$b;
print compare_them([$atitle], [$btitle], 1);
my (@atail, @btail);
while (@$a && $a->[-1] !~ /^\s/) {
unshift @atail, pop @$a;
}
while (@$b && $b->[-1] !~ /^\s/) {
unshift @btail, pop @$b;
}
print compare_them($a, $b);
print compare_them(\@atail, \@btail);
}
sub compare_class {
my ($fromto, $names, $topics) = @_;
my (@where, %where);
for my $name (@$names) {
my $t = $topics->{$name};
my ($a, $b, $in, $force);
if ($t->{OLD} && $t->{NEW}) {
$a = [$t->{OLD}{DESC}];
$b = [$t->{NEW}{DESC}];
if ($t->{OLD}{IN_SECTION} ne $t->{NEW}{IN_SECTION}) {
$force = 1;
$in = '';
} else {
$in = "[$t->{NEW}{IN_SECTION}]";
}
} elsif ($t->{OLD}) {
$a = [$t->{OLD}{DESC}];
$b = [];
$in = "Was in [$t->{OLD}{IN_SECTION}]";
} else {
$a = [];
$b = [$t->{NEW}{DESC}];
$in = "[$t->{NEW}{IN_SECTION}]";
}
next if (defined $a->[0] &&
defined $b->[0] &&
$a->[0] eq $b->[0] && !$force);
if (!exists $where{$in}) {
push @where, $in;
$where{$in} = [];
}
push @{$where{$in}}, [$a, $b];
}
return if (!@where);
for my $in (@where) {
my @bag = @{$where{$in}};
if (defined $fromto && $fromto ne '') {
print "\n", '-' x 50, "\n$fromto\n";
$fromto = undef;
}
print "\n$in\n" if ($in ne '');
for (@bag) {
my ($a, $b) = @{$_};
print "\n";
compare_topics($a, $b);
}
}
}
sub compare_cooking {
my ($old, $new) = @_;
print compare_them([$old->{HEADER}], [$new->{HEADER}]);
print compare_them([$old->{GREETING}], [$new->{GREETING}]);
my (@sections, %sections, @topics, %topics, @fromto, %fromto);
for my $section_name (@{$old->{SECTIONS}}, @{$new->{SECTIONS}}) {
next if (exists $sections{$section_name});
$sections{$section_name} = scalar @sections;
push @sections, $section_name;
}
my $gone_class = "Gone topics";
my $born_class = "Born topics";
my $stay_class = "Other topics";
push @fromto, $born_class;
for my $topic_name (@{$old->{TOPIC_ORDER}}, @{$new->{TOPIC_ORDER}}) {
next if (exists $topics{$topic_name});
push @topics, $topic_name;
my $oldtopic = $old->{TOPICS}{$topic_name};
my $newtopic = $new->{TOPICS}{$topic_name};
$topics{$topic_name} = +{
OLD => $oldtopic,
NEW => $newtopic,
};
my $oldsec = $oldtopic->{IN_SECTION};
my $newsec = $newtopic->{IN_SECTION};
if (defined $oldsec && defined $newsec) {
if ($oldsec ne $newsec) {
my $fromto =
"Moved from [$oldsec] to [$newsec]";
if (!exists $fromto{$fromto}) {
$fromto{$fromto} = [];
push @fromto, $fromto;
}
push @{$fromto{$fromto}}, $topic_name;
} else {
push @{$fromto{$stay_class}}, $topic_name;
}
} elsif (defined $oldsec) {
push @{$fromto{$gone_class}}, $topic_name;
} else {
push @{$fromto{$born_class}}, $topic_name;
}
}
push @fromto, $stay_class;
push @fromto, $gone_class;
for my $fromto (@fromto) {
compare_class($fromto, $fromto{$fromto}, \%topics);
}
}