You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
293 lines
6.3 KiB
293 lines
6.3 KiB
#!/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); |
|
} |
|
}
|
|
|