File Coverage

blib/lib/String/Diff.pm
Criterion Covered Total %
statement 152 155 98.0
branch 72 82 87.8
condition 21 27 77.7
subroutine 16 16 100.0
pod 4 4 100.0
total 265 284 93.3


line stmt bran cond sub pod time code
1             package String::Diff;
2              
3 13     13   1372799 use strict;
  13         33  
  13         301  
4 13     13   61 use warnings;
  13         21  
  13         331  
5 13     13   59 use base qw(Exporter);
  13         27  
  13         561  
6             our @EXPORT_OK = qw( diff_fully diff diff_merge diff_regexp );
7              
8             BEGIN {
9 13     13   1755 local $@;
10 13 100       55 if ($ENV{STRING_DIFF_PP}) {
11 6         13 $@ = 1;
12             } else {
13 7     7   424 eval "use Algorithm::Diff::XS qw( sdiff );"; ## no critic
  7         2643  
  0            
  0            
14             }
15 13 50       1299 if ($@) {
16 13     13   3121 eval "use Algorithm::Diff qw( sdiff );"; ## no critic
  13         31381  
  13         70805  
  13         803  
17 13 50       21175 die $@ if $@;
18             }
19             }
20              
21             our $VERSION = '0.07';
22              
23             our %DEFAULT_MARKS = (
24             remove_open => '[',
25             remove_close => ']',
26             append_open => '{',
27             append_close => '}',
28             separator => '', # for diff_merge
29             );
30              
31             sub diff_fully {
32 126     126 1 417115 my($old, $new, %opts) = @_;
33 126         228 my $old_diff = [];
34 126         200 my $new_diff = [];
35              
36 126 100       353 if ($opts{linebreak}) {
37 30         67 my @diff = sdiff( map{ my @l = map { ( $_, "\n") } split /\n/, $_; pop @l; [ @l ]} $old, $new);
  60         256  
  170         357  
  60         105  
  60         246  
38 30         7378 for my $line (@diff) {
39 166 100       520 if ($line->[0] eq 'c') {
    100          
    100          
40             # change
41 60         137 my($old_diff_tmp, $new_diff_tmp) = _fully($line->[1], $line->[2]);
42 60         87 push @{ $old_diff }, @{ $old_diff_tmp };
  60         89  
  60         141  
43 60         78 push @{ $new_diff }, @{ $new_diff_tmp };
  60         79  
  60         246  
44             } elsif ($line->[0] eq '-') {
45             # remove
46 32         40 push @{ $old_diff }, ['-', $line->[1]];
  32         106  
47             } elsif ($line->[0] eq '+') {
48             # append
49 20         25 push @{ $new_diff }, ['+', $line->[2]];
  20         90  
50             } else {
51             # unchage
52 54         65 push @{ $old_diff }, ['u', $line->[1]];
  54         126  
53 54         71 push @{ $new_diff }, ['u', $line->[2]];
  54         137  
54             }
55             }
56             } else {
57 96         222 ($old_diff, $new_diff) = _fully($old, $new);
58             }
59 126 100       555 wantarray ? ($old_diff, $new_diff) : [ $old_diff, $new_diff];
60             }
61              
62             sub _fully {
63 156     156   249 my($old, $new) = @_;
64 156 100 100     516 return ([], []) unless $old || $new;
65 154         230 my @old_diff = ();
66 154         191 my @new_diff = ();
67 154         199 my $old_str;
68             my $new_str;
69              
70 154 100       236 my @diff = sdiff( map{ $_ ? [ split //, $_ ] : [] } $old, $new);
  308         5673  
71 154         504590 my $last_mode = $diff[0]->[0];
72 154         312 for my $line (@diff) {
73 7728 100       24493 if ($last_mode ne $line->[0]) {
74 1032 50       3345 push @old_diff, [$last_mode, $old_str] if defined $old_str;
75 1032 50       3461 push @new_diff, [$last_mode, $new_str] if defined $new_str;
76              
77             # skip concut ## i forget s mark
78 1032 50       3039 push @old_diff, ['s', ''] unless defined $old_str;
79 1032 50       2086 push @new_diff, ['s', ''] unless defined $new_str;
80              
81 1032         1289 $old_str = $new_str = undef;
82             }
83            
84 7728         8847 $old_str .= $line->[1];
85 7728         9640 $new_str .= $line->[2];
86 7728         11592 $last_mode = $line->[0];
87             }
88 154 50       482 push @old_diff, [$last_mode, $old_str] if defined $old_str;
89 154 50       453 push @new_diff, [$last_mode, $new_str] if defined $new_str;
90              
91 154         398 @old_diff = _fully_filter('-', @old_diff);
92 154         383 @new_diff = _fully_filter('+', @new_diff);
93              
94 154         2174 return (\@old_diff, \@new_diff);
95             }
96              
97             sub _fully_filter {
98 308     308   699 my($c_mode, @diff) = @_;
99 308         420 my @filter = ();
100 308         792 my $last_line = ['', ''];
101              
102 308         496 for my $line (@diff) {
103 2372 100       4448 $line->[0] = $c_mode if $line->[0] eq 'c';
104 2372 100       4505 if ($last_line->[0] eq $line->[0]) {
105 112         186 $last_line->[1] .= $line->[1];
106 112         180 next;
107             }
108 2260 100       5071 push @filter, $last_line if length $last_line->[1];
109 2260         2971 $last_line = $line;
110             }
111 308 100       775 push @filter, $last_line if length $last_line->[1];
112            
113 308         1307 @filter;
114             }
115              
116             sub diff {
117 45     45 1 199413 my($old, $new, %opts) = @_;
118 45         141 my($old_diff, $new_diff) = diff_fully($old, $new, %opts);
119 45         289 %opts = (%DEFAULT_MARKS, %opts);
120              
121 45         155 my $old_str = _str($old_diff, %opts);
122 45         140 my $new_str = _str($new_diff, %opts);
123              
124 45 100       456 wantarray ? ($old_str, $new_str) : [ $old_str, $new_str];
125             }
126              
127             sub _str {
128 90     90   254 my($diff, %opts) = @_;
129 90         133 my $str = '';
130              
131 90         85 my $escape;
132 90 100 66     232 if ($opts{escape} && ref($opts{escape}) eq 'CODE') {
133 2         3 $escape = $opts{escape};
134             }
135 90         94 for my $parts (@{ $diff }) {
  90         162  
136 758 100       1332 my $word = $escape ? $escape->($parts->[1]) : $parts->[1];
137 758 100       1715 if ($parts->[0] eq '-') {
    100          
138 97         283 $str .= "$opts{remove_open}$word$opts{remove_close}";
139             } elsif ($parts->[0] eq '+') {
140 177         435 $str .= "$opts{append_open}$word$opts{append_close}";
141             } else {
142 484         821 $str .= $word;
143             }
144             }
145 90         243 $str;
146             }
147              
148             sub diff_merge {
149 55     55 1 215133 my($old, $new, %opts) = @_;
150 55         200 my($old_diff, $new_diff) = diff_fully($old, $new, %opts);
151 55         689 %opts = (%DEFAULT_MARKS, %opts);
152              
153 55         141 my $old_c = 0;
154 55         72 my $new_c = 0;
155 55         78 my $str = '';
156              
157 55         59 my $escape;
158 55 100 66     253 if ($opts{regexp}) {
    100          
159 20     380   76 $escape = sub { quotemeta($_[0]) };
  380         724  
160             } elsif ($opts{escape} && ref($opts{escape}) eq 'CODE') {
161 3         4 $escape = $opts{escape};
162             }
163             LOOP:
164 55   100     72 while (scalar(@{ $old_diff }) > $old_c && scalar(@{ $new_diff }) > $new_c) {
  558         1567  
  508         1571  
165 503 100       1131 my $old_str = $escape ? $escape->($old_diff->[$old_c]->[1]) : $old_diff->[$old_c]->[1];
166 503 100       1048 my $new_str = $escape ? $escape->($new_diff->[$new_c]->[1]) : $new_diff->[$new_c]->[1];
167              
168 503 100 100     3610 if ($old_diff->[$old_c]->[0] eq 'u' && $new_diff->[$new_c]->[0] eq 'u') {
    100 100        
    100 66        
    50 33        
169 276         575 $str .= $old_str;
170 276         276 $old_c++;
171 276         346 $new_c++;
172             } elsif ($old_diff->[$old_c]->[0] eq '-' && $new_diff->[$new_c]->[0] eq '+') {
173 67         153 $str .= "$opts{remove_open}$old_str";
174 67 100       218 $str .= "$opts{remove_close}$opts{separator}$opts{append_open}" unless $opts{regexp};
175 67 100       187 $str .= $opts{separator} if $opts{regexp};
176 67         365 $str .= "$new_str$opts{append_close}";
177 67         97 $old_c++;
178 67         561 $new_c++;
179             } elsif ($old_diff->[$old_c]->[0] eq 'u' && $new_diff->[$new_c]->[0] eq '+') {
180 114         283 $str .= "$opts{append_open}$new_str$opts{append_close}";
181 114         182 $new_c++;
182             } elsif ($old_diff->[$old_c]->[0] eq '-' && $new_diff->[$new_c]->[0] eq 'u') {
183 46         131 $str .= "$opts{remove_open}$old_str$opts{remove_close}";
184 46         77 $old_c++;
185             }
186             }
187              
188 55         212 $str .= _list_gc($old_diff, $old_c, %opts);
189 55         378 $str .= _list_gc($new_diff, $new_c, %opts);
190              
191 55         621 $str;
192             }
193              
194             sub _list_gc {
195 110     110   388 my($diff, $c, %opts) = @_;
196 110         167 my $str = '';
197              
198 110         126 my $escape;
199 110 100 66     406 if ($opts{regexp}) {
    100          
200 40     6   117 $escape = sub { quotemeta($_[0]) };
  6         15  
201             } elsif ($opts{escape} && ref($opts{escape}) eq 'CODE') {
202 6         10 $escape = $opts{escape};
203             }
204 110         127 while (scalar(@{ $diff }) > $c) {
  132         325  
205 22 100       63 my $_str = $escape ? $escape->($diff->[$c]->[1]) : $diff->[$c]->[1];
206 22 100       141 if ($diff->[$c]->[0] eq '-') {
    50          
207 9         30 $str .= "$opts{remove_open}$_str$opts{remove_close}";
208             } elsif ($diff->[$c]->[0] eq '+') {
209 13         45 $str .= "$opts{append_open}$_str$opts{append_close}";
210             } else {
211 0         0 $str .= $_str;
212             }
213 22         30 $c++;
214             }
215 110         670 $str;
216             }
217              
218             my %regexp_opts = (
219             remove_open => '(?:',
220             remove_close => ')',
221             append_open => '(?:',
222             append_close => ')',
223             separator => '|',
224             regexp => 1,
225             escape => undef,
226             );
227              
228             sub diff_regexp {
229 20     20 1 129096 my($old, $new, %opts) = @_;
230 20         93 diff_merge($old, $new, %opts, %regexp_opts);
231             }
232              
233             1;
234             __END__