line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Text::WordDiff; |
2
|
|
|
|
|
|
|
|
3
|
4
|
|
|
4
|
|
202805
|
use strict; |
|
4
|
|
|
|
|
21
|
|
|
4
|
|
|
|
|
109
|
|
4
|
4
|
|
|
4
|
|
17
|
use vars qw(@ISA $VERSION); |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
186
|
|
5
|
4
|
|
|
4
|
|
2168
|
use Algorithm::Diff (); |
|
4
|
|
|
|
|
17049
|
|
|
4
|
|
|
|
|
77
|
|
6
|
4
|
|
|
4
|
|
391
|
use IO::File; |
|
4
|
|
|
|
|
6783
|
|
|
4
|
|
|
|
|
403
|
|
7
|
4
|
|
|
4
|
|
23
|
use Carp; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
334
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
$VERSION = '0.09'; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# _Mastering Regular Expressions_, p. 132. |
12
|
|
|
|
|
|
|
my $BEGIN_WORD = $] >= 5.006 |
13
|
4
|
|
|
4
|
|
2007
|
? qr/(?:(?
|
|
4
|
|
|
|
|
49
|
|
|
4
|
|
|
|
|
45
|
|
14
|
|
|
|
|
|
|
: qr/(?:(?
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
my %styles = ( |
17
|
|
|
|
|
|
|
ANSIColor => undef, |
18
|
|
|
|
|
|
|
HTML => undef, |
19
|
|
|
|
|
|
|
HTMLTwoLines => undef, |
20
|
|
|
|
|
|
|
); |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
sub import { |
23
|
4
|
|
|
4
|
|
42
|
my $caller = caller; |
24
|
4
|
|
|
4
|
|
75813
|
no strict 'refs'; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
3449
|
|
25
|
4
|
|
|
|
|
9
|
*{"$caller\::word_diff"} = \&word_diff; |
|
4
|
|
|
|
|
67
|
|
26
|
|
|
|
|
|
|
} |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
sub word_diff ($$;$) { |
29
|
35
|
|
|
35
|
0
|
3341
|
my @seqs = ( shift, shift ); |
30
|
35
|
100
|
|
|
|
93
|
my $opts = $_[0] ? { %{ +shift } } : {}; |
|
28
|
|
|
|
|
219
|
|
31
|
35
|
|
50
|
|
|
201
|
$opts->{FILENAME_PREFIX_A} ||= '---'; |
32
|
35
|
|
50
|
|
|
146
|
$opts->{FILENAME_PREFIX_B} ||= '+++'; |
33
|
35
|
|
|
|
|
47
|
my $AorB = 'A'; |
34
|
|
|
|
|
|
|
|
35
|
35
|
|
|
|
|
59
|
for my $seq (@seqs) { |
36
|
70
|
|
|
|
|
114
|
my $type = ref $seq; |
37
|
|
|
|
|
|
|
|
38
|
70
|
|
|
|
|
138
|
while ( $type eq 'CODE' ) { |
39
|
6
|
|
|
|
|
15
|
$seq = $seq->( $opts ); |
40
|
6
|
|
|
|
|
23
|
$type = ref $seq; |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# Get a handle on options. |
44
|
70
|
|
|
|
|
196
|
my $filename = \$opts->{"FILENAME_$AorB"}; |
45
|
70
|
|
|
|
|
137
|
my $mtime = \$opts->{"MTIME_$AorB"}; |
46
|
|
|
|
|
|
|
|
47
|
70
|
100
|
66
|
|
|
190
|
if ( $type eq 'ARRAY' ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
48
|
|
|
|
|
|
|
# The work has already been done for us. |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
elsif ( $type eq 'SCALAR' ) { |
52
|
|
|
|
|
|
|
# Parse the words from the string. |
53
|
43
|
|
|
|
|
717
|
$seq = [ split $BEGIN_WORD, $$seq ]; |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
elsif ( !$type ) { |
57
|
|
|
|
|
|
|
# Assume that it's a raw file name. |
58
|
6
|
50
|
|
|
|
18
|
$$filename = $seq unless defined $$filename; |
59
|
6
|
50
|
|
|
|
83
|
$$mtime = (stat $seq)[9] unless defined $$mtime; |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# Parse the words from the file. |
62
|
6
|
|
|
|
|
49
|
my $seq_fh = IO::File->new($seq, '<'); |
63
|
6
|
|
|
|
|
600
|
$seq = do { local $/; [ split $BEGIN_WORD, <$seq_fh> ] }; |
|
6
|
|
|
|
|
25
|
|
|
6
|
|
|
|
|
666
|
|
64
|
6
|
|
|
|
|
44
|
$seq_fh->close; |
65
|
|
|
|
|
|
|
} |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
elsif ( $type eq "GLOB" || UNIVERSAL::isa( $seq, "IO::Handle" ) ) { |
68
|
|
|
|
|
|
|
# Parse the words from the file. |
69
|
12
|
|
|
|
|
17
|
$seq = do { local $/; [ split $BEGIN_WORD, <$seq> ] }; |
|
12
|
|
|
|
|
33
|
|
|
12
|
|
|
|
|
1329
|
|
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
else { |
73
|
|
|
|
|
|
|
# Damn. |
74
|
0
|
|
|
|
|
0
|
confess "Can't handle input of type $type"; |
75
|
|
|
|
|
|
|
} |
76
|
70
|
|
|
|
|
373
|
$AorB++; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
# Set up the output handler. |
80
|
35
|
|
|
|
|
45
|
my $output; |
81
|
35
|
|
|
|
|
57
|
my $out_handler = delete $opts->{OUTPUT}; |
82
|
35
|
|
|
|
|
54
|
my $type = ref $out_handler ; |
83
|
|
|
|
|
|
|
|
84
|
35
|
100
|
33
|
|
|
115
|
if ( ! defined $out_handler ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# Default to concatenating a string. |
86
|
23
|
|
|
|
|
33
|
$output = ''; |
87
|
23
|
|
|
549
|
|
83
|
$out_handler = sub { $output .= shift }; |
|
549
|
|
|
|
|
3553
|
|
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
elsif ( $type eq 'CODE' ) { |
90
|
|
|
|
|
|
|
# We'll just use the handler. |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
elsif ( $type eq 'SCALAR' ) { |
93
|
|
|
|
|
|
|
# Append to the scalar reference. |
94
|
3
|
|
|
|
|
17
|
my $out_ref = $out_handler; |
95
|
3
|
|
|
27
|
|
15
|
$out_handler = sub { $$out_ref .= shift }; |
|
27
|
|
|
|
|
180
|
|
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
elsif ( $type eq 'ARRAY' ) { |
98
|
|
|
|
|
|
|
# Push each item onto the array. |
99
|
3
|
|
|
|
|
6
|
my $out_ref = $out_handler; |
100
|
3
|
|
|
27
|
|
15
|
$out_handler = sub { push @$out_ref, shift }; |
|
27
|
|
|
|
|
162
|
|
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
elsif ( $type eq 'GLOB' || UNIVERSAL::isa( $out_handler, 'IO::Handle' )) { |
103
|
|
|
|
|
|
|
# print to the file handle. |
104
|
3
|
|
|
|
|
5
|
my $output_handle = $out_handler; |
105
|
3
|
|
|
27
|
|
15
|
$out_handler = sub { print $output_handle shift }; |
|
27
|
|
|
|
|
179
|
|
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
else { |
108
|
|
|
|
|
|
|
# D'oh! |
109
|
0
|
|
|
|
|
0
|
croak "Unrecognized output type: $type"; |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# Instantiate the diff object, along with any options. |
113
|
35
|
|
|
|
|
164
|
my $diff = Algorithm::Diff->new(@seqs, delete $opts->{DIFF_OPTS}); |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
# Load the style class and instantiate an instance. |
116
|
35
|
|
100
|
|
|
21640
|
my $style = delete $opts->{STYLE} || 'ANSIColor'; |
117
|
35
|
100
|
|
|
|
118
|
$style = __PACKAGE__ . "::$style" if exists $styles{$style}; |
118
|
35
|
50
|
0
|
|
|
277
|
eval "require $style" or die $@ unless $style->can('new'); |
119
|
35
|
50
|
|
|
|
121
|
$style = $style->new($opts) if !ref $style; |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
# Run the diff. |
122
|
35
|
|
|
|
|
57
|
my $hunks = 0; |
123
|
35
|
|
|
|
|
85
|
$out_handler->($style->file_header()); |
124
|
35
|
|
|
|
|
108
|
while ($diff->Next) { |
125
|
169
|
|
|
|
|
3352
|
$hunks++; |
126
|
169
|
|
|
|
|
274
|
$out_handler->( $style->hunk_header() ); |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
# Output unchanged items. |
129
|
169
|
100
|
|
|
|
290
|
if (my @same = $diff->Same) { |
130
|
89
|
|
|
|
|
2482
|
$out_handler->( $style->same_items(@same) ); |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# Output deleted and inserted items. |
134
|
|
|
|
|
|
|
else { |
135
|
80
|
50
|
|
|
|
770
|
if (my @del = $diff->Items(1)) { |
136
|
80
|
|
|
|
|
1249
|
$out_handler->( $style->delete_items(@del) ); |
137
|
|
|
|
|
|
|
} |
138
|
80
|
50
|
|
|
|
220
|
if (my @ins = $diff->Items(2)) { |
139
|
80
|
|
|
|
|
1233
|
$out_handler->( $style->insert_items(@ins) ); |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
} |
142
|
169
|
|
|
|
|
477
|
$out_handler->( $style->hunk_footer() ); |
143
|
|
|
|
|
|
|
} |
144
|
35
|
|
|
|
|
862
|
$out_handler->( $style->file_footer() ); |
145
|
|
|
|
|
|
|
|
146
|
35
|
100
|
|
|
|
430
|
return defined $output ? $output : $hunks; |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
package Text::WordDiff::Base; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
sub new { |
152
|
35
|
|
|
35
|
|
64
|
my ($class, $opts) = @_; |
153
|
35
|
|
|
|
|
41
|
return bless { %{$opts} } => $class; |
|
35
|
|
|
|
|
206
|
|
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
sub file_header { |
158
|
23
|
|
|
23
|
|
114
|
my $self = shift; |
159
|
23
|
|
|
|
|
51
|
my $fn1 = $self->filename_a; |
160
|
23
|
|
|
|
|
55
|
my $fn2 = $self->filename_b; |
161
|
23
|
100
|
66
|
|
|
89
|
return '' unless defined $fn1 && defined $fn2; |
162
|
|
|
|
|
|
|
|
163
|
3
|
|
|
|
|
323
|
my $p1 = $self->filename_prefix_a; |
164
|
3
|
|
|
|
|
15
|
my $t1 = $self->mtime_a; |
165
|
3
|
|
|
|
|
12
|
my $p2 = $self->filename_prefix_b; |
166
|
3
|
|
|
|
|
22
|
my $t2 = $self->mtime_b; |
167
|
|
|
|
|
|
|
|
168
|
3
|
50
|
|
|
|
455
|
return "$p1 $fn1" . (defined $t1 ? "\t" . localtime $t1 : '') . "\n" |
|
|
50
|
|
|
|
|
|
169
|
|
|
|
|
|
|
. "$p2 $fn2" . (defined $t2 ? "\t" . localtime $t2 : '') . "\n" |
170
|
|
|
|
|
|
|
; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
57
|
|
|
57
|
|
95
|
sub hunk_header { return '' } |
174
|
1
|
|
|
1
|
|
3
|
sub same_items { return '' } |
175
|
1
|
|
|
1
|
|
2
|
sub insert_items { return '' } |
176
|
1
|
|
|
1
|
|
3
|
sub delete_items { return '' } |
177
|
57
|
|
|
57
|
|
87
|
sub hunk_footer { return '' } |
178
|
12
|
|
|
12
|
|
24
|
sub file_footer { return '' } |
179
|
34
|
|
|
34
|
|
67
|
sub filename_a { return shift->{FILENAME_A} } |
180
|
34
|
|
|
34
|
|
50
|
sub filename_b { return shift->{FILENAME_B} } |
181
|
4
|
|
|
4
|
|
9
|
sub mtime_a { return shift->{MTIME_A} } |
182
|
4
|
|
|
4
|
|
10
|
sub mtime_b { return shift->{MTIME_B} } |
183
|
4
|
|
|
4
|
|
14
|
sub filename_prefix_a { return shift->{FILENAME_PREFIX_A} } |
184
|
4
|
|
|
4
|
|
9
|
sub filename_prefix_b { return shift->{FILENAME_PREFIX_B} } |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
1; |
187
|
|
|
|
|
|
|
__END__ |