296 lines
6.4 KiB
Perl
Executable File
296 lines
6.4 KiB
Perl
Executable File
#!/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);
|
|
}
|
|
}
|