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             # git description: v0.10-1-gbcee628
3              
4             our $AUTHORITY = 'cpan:SCHWIGON';
5             $String::Diff::VERSION = '0.11';
6 14     14   2153865 use strict;
  14         33  
  14         586  
7 14     14   78 use warnings;
  14         23  
  14         904  
8 14     14   82 use base qw(Exporter);
  14         37  
  14         1287  
9             our @EXPORT_OK = qw( diff_fully diff diff_merge diff_regexp );
10              
11             BEGIN {
12 14     14   2951 local $@;
13 14 100       116 if ($ENV{STRING_DIFF_PP}) {
14 6         21 $@ = 1;
15             } else {
16 8     8   700 eval "use Algorithm::Diff::XS qw( sdiff );"; ## no critic
  8         1336  
  0         0  
  0         0  
17             }
18 14 50       265 if ($@) {
19 14     14   1105 eval "use Algorithm::Diff qw( sdiff );"; ## no critic
  14         9153  
  14         85952  
  14         1104  
20 14 50       27053 die $@ if $@;
21             }
22             }
23              
24             our %DEFAULT_MARKS = (
25             remove_open => '[',
26             remove_close => ']',
27             append_open => '{',
28             append_close => '}',
29             separator => '', # for diff_merge
30             );
31              
32             sub diff_fully {
33 126     126 1 1354784 my($old, $new, %opts) = @_;
34 126         284 my $old_diff = [];
35 126         243 my $new_diff = [];
36              
37 126 100       461 if ($opts{linebreak}) {
38 30         85 my @diff = sdiff( map{ my @l = map { ( $_, "\n") } split /\n/, $_; pop @l; [ @l ]} $old, $new);
  60         272  
  170         354  
  60         116  
  60         260  
39 30         9147 for my $line (@diff) {
40 166 100       460 if ($line->[0] eq 'c') {
    100          
    100          
41             # change
42 60         219 my($old_diff_tmp, $new_diff_tmp) = _fully($line->[1], $line->[2]);
43 60         109 push @{ $old_diff }, @{ $old_diff_tmp };
  60         107  
  60         197  
44 60         95 push @{ $new_diff }, @{ $new_diff_tmp };
  60         87  
  60         258  
45             } elsif ($line->[0] eq '-') {
46             # remove
47 32         44 push @{ $old_diff }, ['-', $line->[1]];
  32         122  
48             } elsif ($line->[0] eq '+') {
49             # append
50 20         63 push @{ $new_diff }, ['+', $line->[2]];
  20         85  
51             } else {
52             # unchage
53 54         86 push @{ $old_diff }, ['u', $line->[1]];
  54         125  
54 54         75 push @{ $new_diff }, ['u', $line->[2]];
  54         123  
55             }
56             }
57             } else {
58 96         345 ($old_diff, $new_diff) = _fully($old, $new);
59             }
60 126 100       760 wantarray ? ($old_diff, $new_diff) : [ $old_diff, $new_diff];
61             }
62              
63             sub _fully {
64 156     156   428 my($old, $new) = @_;
65 156 100 100     634 return ([], []) unless $old || $new;
66 154         303 my @old_diff = ();
67 154         306 my @new_diff = ();
68 154         315 my $old_str;
69             my $new_str;
70              
71 154 100       369 my @diff = sdiff( map{ $_ ? [ split //, $_ ] : [] } $old, $new);
  308         6661  
72 154         588752 my $last_mode = $diff[0]->[0];
73 154         469 for my $line (@diff) {
74 7728 100       12913 if ($last_mode ne $line->[0]) {
75 1032 50       2742 push @old_diff, [$last_mode, $old_str] if defined $old_str;
76 1032 50       2487 push @new_diff, [$last_mode, $new_str] if defined $new_str;
77              
78             # skip concut ## i forget s mark
79 1032 50       1887 push @old_diff, ['s', ''] unless defined $old_str;
80 1032 50       1917 push @new_diff, ['s', ''] unless defined $new_str;
81              
82 1032         1461 $old_str = $new_str = undef;
83             }
84            
85 7728         9999 $old_str .= $line->[1];
86 7728         9655 $new_str .= $line->[2];
87 7728         10565 $last_mode = $line->[0];
88             }
89 154 50       540 push @old_diff, [$last_mode, $old_str] if defined $old_str;
90 154 50       551 push @new_diff, [$last_mode, $new_str] if defined $new_str;
91              
92 154         527 @old_diff = _fully_filter('-', @old_diff);
93 154         358 @new_diff = _fully_filter('+', @new_diff);
94              
95 154         2259 return (\@old_diff, \@new_diff);
96             }
97              
98             sub _fully_filter {
99 308     308   854 my($c_mode, @diff) = @_;
100 308         480 my @filter = ();
101 308         640 my $last_line = ['', ''];
102              
103 308         636 for my $line (@diff) {
104 2372 100       4111 $line->[0] = $c_mode if $line->[0] eq 'c';
105 2372 100       3995 if ($last_line->[0] eq $line->[0]) {
106 112         221 $last_line->[1] .= $line->[1];
107 112         231 next;
108             }
109 2260 100       3900 push @filter, $last_line if length $last_line->[1];
110 2260         3009 $last_line = $line;
111             }
112 308 100       658 push @filter, $last_line if length $last_line->[1];
113            
114 308         1230 @filter;
115             }
116              
117             sub diff {
118 45     45 1 1447404 my($old, $new, %opts) = @_;
119 45         176 my($old_diff, $new_diff) = diff_fully($old, $new, %opts);
120 45         441 %opts = (%DEFAULT_MARKS, %opts);
121              
122 45         196 my $old_str = _str($old_diff, %opts);
123 45         145 my $new_str = _str($new_diff, %opts);
124              
125 45 100       496 wantarray ? ($old_str, $new_str) : [ $old_str, $new_str];
126             }
127              
128             sub _str {
129 90     90   298 my($diff, %opts) = @_;
130 90         149 my $str = '';
131              
132 90         118 my $escape;
133 90 100 66     276 if ($opts{escape} && ref($opts{escape}) eq 'CODE') {
134 2         3 $escape = $opts{escape};
135             }
136 90         178 for my $parts (@{ $diff }) {
  90         166  
137 758 100       1294 my $word = $escape ? $escape->($parts->[1]) : $parts->[1];
138 758 100       1505 if ($parts->[0] eq '-') {
    100          
139 97         261 $str .= "$opts{remove_open}$word$opts{remove_close}";
140             } elsif ($parts->[0] eq '+') {
141 177         346 $str .= "$opts{append_open}$word$opts{append_close}";
142             } else {
143 484         849 $str .= $word;
144             }
145             }
146 90         273 $str;
147             }
148              
149             sub diff_merge {
150 55     55 1 1024299 my($old, $new, %opts) = @_;
151 55         268 my($old_diff, $new_diff) = diff_fully($old, $new, %opts);
152 55         572 %opts = (%DEFAULT_MARKS, %opts);
153              
154 55         171 my $old_c = 0;
155 55         87 my $new_c = 0;
156 55         99 my $str = '';
157              
158 55         88 my $escape;
159 55 100 66     291 if ($opts{regexp}) {
    100          
160 20     380   81 $escape = sub { quotemeta($_[0]) };
  380         505  
161             } elsif ($opts{escape} && ref($opts{escape}) eq 'CODE') {
162 3         4 $escape = $opts{escape};
163             }
164             LOOP:
165 55   100     93 while (scalar(@{ $old_diff }) > $old_c && scalar(@{ $new_diff }) > $new_c) {
  558         1071  
  508         1126  
166 503 100       885 my $old_str = $escape ? $escape->($old_diff->[$old_c]->[1]) : $old_diff->[$old_c]->[1];
167 503 100       962 my $new_str = $escape ? $escape->($new_diff->[$new_c]->[1]) : $new_diff->[$new_c]->[1];
168              
169 503 100 100     2155 if ($old_diff->[$old_c]->[0] eq 'u' && $new_diff->[$new_c]->[0] eq 'u') {
    100 100        
    100 66        
    50 33        
170 276         435 $str .= $old_str;
171 276         326 $old_c++;
172 276         324 $new_c++;
173             } elsif ($old_diff->[$old_c]->[0] eq '-' && $new_diff->[$new_c]->[0] eq '+') {
174 67         173 $str .= "$opts{remove_open}$old_str";
175 67 100       205 $str .= "$opts{remove_close}$opts{separator}$opts{append_open}" unless $opts{regexp};
176 67 100       150 $str .= $opts{separator} if $opts{regexp};
177 67         130 $str .= "$new_str$opts{append_close}";
178 67         93 $old_c++;
179 67         93 $new_c++;
180             } elsif ($old_diff->[$old_c]->[0] eq 'u' && $new_diff->[$new_c]->[0] eq '+') {
181 114         212 $str .= "$opts{append_open}$new_str$opts{append_close}";
182 114         140 $new_c++;
183             } elsif ($old_diff->[$old_c]->[0] eq '-' && $new_diff->[$new_c]->[0] eq 'u') {
184 46         103 $str .= "$opts{remove_open}$old_str$opts{remove_close}";
185 46         89 $old_c++;
186             }
187             }
188              
189 55         244 $str .= _list_gc($old_diff, $old_c, %opts);
190 55         172 $str .= _list_gc($new_diff, $new_c, %opts);
191              
192 55         605 $str;
193             }
194              
195             sub _list_gc {
196 110     110   412 my($diff, $c, %opts) = @_;
197 110         221 my $str = '';
198              
199 110         150 my $escape;
200 110 100 66     363 if ($opts{regexp}) {
    100          
201 40     6   102 $escape = sub { quotemeta($_[0]) };
  6         11  
202             } elsif ($opts{escape} && ref($opts{escape}) eq 'CODE') {
203 6         7 $escape = $opts{escape};
204             }
205 110         173 while (scalar(@{ $diff }) > $c) {
  132         270  
206 22 100       53 my $_str = $escape ? $escape->($diff->[$c]->[1]) : $diff->[$c]->[1];
207 22 100       122 if ($diff->[$c]->[0] eq '-') {
    50          
208 9         21 $str .= "$opts{remove_open}$_str$opts{remove_close}";
209             } elsif ($diff->[$c]->[0] eq '+') {
210 13         32 $str .= "$opts{append_open}$_str$opts{append_close}";
211             } else {
212 0         0 $str .= $_str;
213             }
214 22         27 $c++;
215             }
216 110         340 $str;
217             }
218              
219             my %regexp_opts = (
220             remove_open => '(?:',
221             remove_close => ')',
222             append_open => '(?:',
223             append_close => ')',
224             separator => '|',
225             regexp => 1,
226             escape => undef,
227             );
228              
229             sub diff_regexp {
230 20     20 1 743555 my($old, $new, %opts) = @_;
231 20         116 diff_merge($old, $new, %opts, %regexp_opts);
232             }
233              
234             1;
235              
236             __END__