
1 changed files with 293 additions and 0 deletions
@ -0,0 +1,293 @@
@@ -0,0 +1,293 @@
|
||||
#!/usr/bin/perl -w |
||||
|
||||
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); |
||||
} |
||||
} |
Loading…
Reference in new issue