Staging
v0.5.1
swh:1:snp:c5feb7ee9221a3820c8879e85e8a18470c0b3afa
Raw File
Tip revision: 181785487ecd48c7a3760ca31a6a245aabb2cbc8 authored by Junio C Hamano on 22 January 2022, 03:05:07 UTC
What's cooking (2022/01 #06)
Tip revision: 1817854
compare-cooking.perl
#!/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);
	}
}
back to top