App-ccdiff
view release on metacpan or search on metacpan
my $rev_color = $rc{bg};
my $cli_color = $ENV{CLICOLOR}; # https://bixense.com/clicolors/
my $no_colors = $ENV{NO_COLOR}; # https://no-color.org
my $old_label = $rc{old_label};
my $new_label = $rc{new_label};
my $list_colors;
my $diff_class;
if ($no_colors) {
$ENV{CLICOLOR_FORCE} and $no_colors = 0;
}
elsif (defined $cli_color) {
# true $cli_color is the default for ccdiff
!$cli_color || !-t and $no_colors = 1;
}
unless (caller) {
$ENV{CCDIFF_OPTIONS} and unshift @ARGV, split m/\s+/ => $ENV{CCDIFF_OPTIONS};
GetOptions (
"help|?" => sub { usage (0); },
"V|version" => sub { say "$CMD [$VERSION]"; exit 0; },
"man" => sub { pod_nroff (); },
"info" => sub { pod_text (); },
"U|utf-8!" => \$opt_U,
"dc|diff-class=s" => \$diff_class,
"pp!" => sub { $diff_class = "PP" },
# "c|context:3" => \$opt_c, # implement context-diff?
"u|unified:3" => \$opt_u,
"I|idx|index:-1" => \$opt_I,
"t|threshold=i" => \$opt_t,
"H|header!" => \$opt_H,
"HC|header-color=s" => \$opt_H,
"h|heuristics=i" => \$opt_h,
"e|ellipsis=i" => \$opt_e,
"emacs!" => \$emacs,
"a|ascii" => sub { $opt_a ^= 1 },
"m|markers" => sub { $opt_m ^= 1 },
"r|reverse|invert" => sub { $opt_r ^= 1 },
"s|swap!" => sub { $opt_s ^= 1 },
"i|ignore-case!" => \$opt_i,
"w|ignore-all-space!" => \$opt_w,
"b|ignore-ws|ignore-space-change!" => \$opt_b,
"Z|ignore-trailing-space!" => \$opt_Z,
"E|ignore-tab-expansion!" => \$opt_E, # NYI
"B|ignore-blank-lines!" => \$opt_B, # Partly implemented
"old-label|label-old=s" => \$old_label,
"new-label|label-new=s" => \$new_label,
"p|pink!" => sub { $old_color = "magenta" },
"old=s" => \$old_color,
"new=s" => \$new_color,
"bg=s" => \$rev_color,
"no-colors" => \$no_colors,
"list-colors!" => \$list_colors,
"settings|defaults" => sub {
binmode STDOUT, ":encoding(utf-8)";
printf "%-10s : %s\n", $_, $rc{$_} // "<undef>" for sort keys %rc;
exit 0;
},
"v|verbose:1" => \$opt_v,
) or usage (1);
}
$opt_w and $opt_b = $opt_Z = $opt_E = $opt_B = 1;
$opt_h >= 1 and $opt_h /= 100;
sub pod_text {
require Pod::Text::Color;
my $m = $no_colors ? "Pod::Text" : "Pod::Text::Color";
my $p = $m->new ();
open my $fh, ">", \my $out;
$p->parse_from_file ($0, $fh);
close $fh;
print $out;
exit 0;
} # pod_text
sub pod_nroff {
first { -x "$_/nroff" } grep { -d } split m/:+/ => $ENV{PATH} or pod_text ();
require Pod::Man;
my $p = Pod::Man->new ();
open my $fh, "|-", "nroff", "-man";
$p->parse_from_file ($0, $fh);
close $fh;
exit 0;
} # pod_nroff
# Color initialization
for ($old_color, $new_color, $rev_color) {
s/^(.*)[ _]bold$/bold $1/i;
s/^bold_/bold /i;
}
my %clr = map { $_ => color (s{^(.*)[ _]bold$}{bold $1}ir =~
s{^bold[ _]}{bold }ir) }
map {( $_, "on_$_", "bold $_" )}
qw( red green blue black white cyan magenta yellow );
$clr{$_} //= color ($_) for tac_colors ();
$no_colors and $clr{$_} = "" for keys %clr;
$clr{none} = $clr{on_none} = "";
my ($reset, $bg_new, $bg_old,
$chr_cml, $chr_cmr, $chr_ctx, $chr_eli, $chr_eql, $chr_lft,
$chr_new, $chr_old, $chr_rgt,
$clr_dbg, $clr_grn, $clr_new, $clr_old, $clr_red, $clr_rev,
$cmp_sub) = (RESET);
if ($list_colors) {
my @clr = map { sprintf "%s%-18s%s", $clr{$_}, $_, $reset } sort keys %clr;
while (@clr) {
say join " " => map { $_ // "" } splice @clr, 0, 4;
}
exit;
}
ccdiff (@ARGV);
}
exit 0;
}
sub ccdiff {
my $f1 = shift or usage (1);
my $f2 = $_[0] // "-";
-b $f1 || -c $f1 || -b $f2 || -c $f2 and
die "Character and block devices are not supported\n";
-d $f1 || -d $f2 and
die "$CMD does not support directory diff\n";
my $fh;
if (@_ > 1 && ref $_[1]) { # optional hash with overruling arguments
my %opt = %{$_[1]};
foreach my $o (keys %opt) {
my $v = $opt{$o};
$o eq "ascii" and $opt_a = $v;
$o eq "bg" and $rev_color = $v;
# $o eq "context" and $opt_c = $v;
$o eq "ellipsis" and $opt_e = $v;
$o eq "emacs" and $emacs = $v;
$o eq "header" and $opt_H = $v;
$o eq "heuristics" and $opt_h = $v;
$o eq "ignore-all-space" and $opt_w = $v;
$o eq "ignore-blank-lines" and $opt_B = $v;
$o eq "ignore-case" and $opt_i = $v;
$o eq "ignore-space-change" and $opt_b = $v;
$o eq "ignore-tab-expansion" and $opt_E = $v;
$o eq "ignore-trailing-space" and $opt_Z = $v;
$o eq "index" and $opt_I = $v;
$o eq "list-colors" and $list_colors = $v;
$o eq "markers" and $opt_m = $v;
$o eq "new" and $new_color = $v;
$o eq "old" and $old_color = $v;
$o eq "new-label" and $new_label = $v;
$o eq "old-label" and $old_label = $v;
$o eq "reverse" and $opt_r = $v;
$o eq "swap" and $opt_s = $v;
$o eq "threshold" and $opt_t = $v;
$o eq "unified" and $opt_u = $v;
$o eq "unified" and $opt_u = $v;
$o eq "utf-8" and $opt_U = $v;
$o eq "verbose" and $opt_v = $v;
if ($o eq "out") {
open $fh, ">", $v or die "Cannot select out: $!\n";
select $fh;
}
}
}
set_options ();
$emacs and @_ == 0 && -f $f1 && -f "$f1~" and ($f1, $f2) = ("$f1~", $f1);
$f1 eq "-" && $f2 eq "-" and usage (1);
binmode STDERR, ":encoding(utf-8)";
if ($opt_U) {
binmode STDIN, ":encoding(utf-8)";
binmode STDOUT, ":encoding(utf-8)";
}
my @d1 = ref $f1 eq "ARRAY" ? @$f1 : $f1 eq "-" ? <STDIN> : do {
open my $fh, "<", $f1 or die "$f1: $!\n";
$opt_U and binmode $fh, ":encoding(utf-8)";
<$fh>;
};
my @d2 = ref $f2 eq "ARRAY" ? @$f2 : $f2 eq "-" ? <STDIN> : do {
open my $fh, "<", $f2 or die "$f2: $!\n";
$opt_U and binmode $fh, ":encoding(utf-8)";
<$fh>;
};
if ($opt_H) {
my $hc = "";
if ($opt_H =~ m/^\w\w+/) {
my ($hfg, $hbg) = split m/_?(?=on_)/ => lc $opt_H =~ s/\s+/_/gr;
$hfg && defined $clr{$hfg} and $hc .= $clr{$hfg};
$hbg && defined $clr{$hbg} and $hc .= $clr{$hbg};
}
my $nl = max length $f1, length $f2, 7;
my $sl = $hc ? ($ENV{COLUMNS} || 80) - 4 - $nl : 1;
my $hi = -1;
my @hi = ($old_label, $new_label);
my @h = map { $hi++; -f $_
? { tag => "",
name => $_,
stamp => scalar localtime ((stat $_)[9]),
}
: { tag => "",
name => $hi[$hi] // "*STDIN",
stamp => scalar localtime,
}
} $f1, $f2;
if (defined $opt_u) {
($h[0]{tag}, $h[1]{tag}) = ("---", "+++");
$sl -= 2;
printf "%s%s %-*s %-*s%s\n", $hc, $_->{tag},
$nl, $_->{name}, $sl, $_->{stamp}, $clr{reset} for @h;
}
#elsif ($opt_c) { # diff -c also provides (ugly) headers, but opt_c is NYI
# }
else {
($h[0]{tag}, $h[1]{tag}) = ("<", ">");
printf "%s%s %-*s %-*s%s\n", $hc, $_->{tag},
$nl, $_->{name}, $sl, $_->{stamp}, $clr{reset} for @h;
}
}
my $diff = $diff_class->new (\@d1, \@d2, $cmp_sub);
$diff->Base (1);
my ($N, $idx, @s) = (0, 0);
while ($diff->Next) {
$N++;
if ($diff->Same) {
if (defined $opt_u) {
@s = $diff->Items (1);
$N > 1 and print "$chr_ctx$_" for grep { defined } @s[0..($opt_u - 1)];
unshift @s, undef while @s < $opt_u;
}
next;
}
my $sep = "";
my @d = map {[ $diff->Items ($_) ]} 1, 2;
my @do = @{$d[0]};
my @dn = @{$d[1]};
if ($opt_B and "@do" !~ m/\S/ && "@dn" !~ m/\S/) {
# Modify @s for -u?
next;
( run in 0.985 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )