File Coverage

blib/lib/Text/Same/TextUI.pm
Criterion Covered Total %
statement 144 146 98.6
branch 40 46 86.9
condition 23 25 92.0
subroutine 16 16 100.0
pod 2 2 100.0
total 225 235 95.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Text::Same::TextUI
4              
5             =head1 DESCRIPTION
6              
7             functions for outputting the results of a comparison made with Text::Same::compare();
8              
9             =head1 SYNOPSIS
10              
11             Usage : use Text::Same::TextUI;
12             ...
13             my $matchmap = compare(\%options, $file1, $file2);
14             my @source1_non_matches = $matchmap->source1_non_matches;
15             my @source2_non_matches = $matchmap->source2_non_matches;
16             draw_non_matches(\%options, \@source1_non_matches, $matchmap->source1);
17             draw_non_matches(\%options, \@source2_non_matches, $matchmap->source2);
18              
19              
20             =head1 METHODS
21              
22             See below. Methods private to this module are prefixed by an
23             underscore.
24              
25             =cut
26              
27             package Text::Same::TextUI;
28              
29 1     1   2428 use Exporter;
  1         3  
  1         49  
30 1     1   6 use vars qw($VERSION @EXPORT @ISA);
  1         3  
  1         98  
31             @ISA = qw( Exporter );
32             @EXPORT = qw( draw_match draw_non_match );
33              
34 1     1   6 use warnings FATAL => 'all';
  1         2  
  1         50  
35 1     1   5 use strict;
  1         2  
  1         40  
36 1     1   6 use Carp;
  1         2  
  1         79  
37              
38             $VERSION = '0.07';
39              
40 1     1   6 use Text::Same::ChunkedSource;
  1         3  
  1         20  
41 1     1   5 use Text::Same::Util;
  1         2  
  1         3029  
42              
43             =head2 draw_non_match
44              
45             Title : draw_non_match
46             Usage : draw_non_match(\%options, $source, $non_match);
47             Function: return a string suitable to output that is a representation of
48             a non matching region (range of chunk indexes) in a particular
49             source
50             Args : %options - settings to use
51             $source - the ChunkedSource that this non-match came from (for
52             looking up the actual chunks/lines for the range of
53             indexes)
54             $non_match - a Range object representing the non-matching chunks
55              
56             =cut
57              
58             sub draw_non_match
59             {
60 30     30 1 39 my $options = shift;
61 30         39 my $source = shift;
62 30         35 my $non_match_range = shift;
63 30         34 my $screen_width = 78;
64              
65 30 50       69 if (defined $options->{term_width}) {
66 0         0 $screen_width = $options->{term_width} - 2;
67             }
68              
69 30         82 my $start = $non_match_range->start();
70 30         77 my $end = $non_match_range->end();
71              
72 30         92 my $ret = " " . ($start+1) . ".." . ($end+1) . ":\n";
73 30         69 my @match_chunks = _get_match_chunks($options, $start, $end, $source);
74              
75 30         40 my $padding = " ";
76              
77 30         46 for my $match_chunk (@match_chunks) {
78 30         62 $match_chunk = substr $match_chunk, 0, $screen_width - length $padding;
79 30         37 $ret .= $padding;
80 30         36 $ret .= $match_chunk;
81 30         60 $ret .= "\n";
82             }
83              
84 30         89 return $ret;
85             }
86              
87             =head2 draw_match
88              
89             Title : draw_match
90             Usage : draw_match(\%options, $match);
91             Function: return a string suitable to output that is a representation of
92             a match between two sources
93             Args : %options - settings to use
94             $match - a Match object representing the matching chunks
95              
96             =cut
97              
98             sub draw_match
99             {
100 70     70 1 21783 my $options = shift;
101 70         89 my $match = shift;
102              
103 70 100       187 if ($options->{side_by_side}) {
104 35         79 _draw_match_side_by_side($options, $match);
105             } else {
106 35         81 _draw_match_vertically($options, $match);
107             }
108             }
109              
110             sub _draw_match_side_by_side
111             {
112 35     35   45 my $options = shift;
113 35         41 my $match = shift;
114              
115 35         93 my $min1 = $match->min1;
116 35         94 my $max1 = $match->max1;
117 35         79 my $min2 = $match->min2;
118 35         83 my $max2 = $match->max2;
119              
120 35         70 my $width = _get_term_width($options);
121              
122 35         79 my $half_width = int($width / 2 - 6);
123              
124 35         110 my $ret = "match " . ($min1+1) . ".." . ($max1+1) . "==" .
125             ($min2+1) . ".." . ($max2+1) . "\n";
126              
127 35         97 my @start_context1 = _get_start_context($options, $min1, $match->source1);
128 35         107 my @start_context2 = _get_start_context($options, $min2, $match->source2);
129 35 50       83 my $max_start_context_len =
130             scalar(@start_context1) > scalar(@start_context2) ?
131             scalar(@start_context1) : scalar(@start_context2);
132              
133 35         100 my $context_format_str =
134             " %-${half_width}.${half_width}s %-${half_width}.${half_width}s\n";
135 35         79 my $match_format_str =
136             " %-${half_width}.${half_width}s %s %-${half_width}.${half_width}s\n";
137 35         37 my $i;
138 35         80 for ($i = 0; $i < $max_start_context_len; $i++) {
139 105 100 100     359 if (defined $start_context1[$i] || defined $start_context2[$i]) {
140 63 100       366 $ret .= sprintf $context_format_str,
    100          
141             (defined $start_context1[$i] ? $start_context1[$i] : ""),
142             (defined $start_context2[$i] ? $start_context2[$i] : "");
143             }
144             }
145              
146 35         111 my @match_chunks1 = _get_match_chunks($options, $min1, $max1,
147             $match->source1);
148 35         97 my @match_chunks2 = _get_match_chunks($options, $min2, $max2,
149             $match->source2);
150 35         43 my $next_indicator = undef;
151              
152 35   100     114 for ($i = 0; $i
153 131         174 my $left_chunk = $match_chunks1[$i];
154 131 100       210 if (defined $left_chunk) {
155 129         168 $left_chunk =~ s/\t/ /g;
156             }
157 131   100     430 my $left_ignorable =
158             defined $left_chunk && is_ignorable($options, $left_chunk);
159 131         183 my $right_chunk = $match_chunks2[$i];
160 131 100       244 if ($right_chunk) {
161 108         151 $right_chunk =~ s/\t/ /g;
162             }
163 131   100     372 my $right_ignorable =
164             defined $right_chunk && is_ignorable($options, $right_chunk);
165              
166 131         145 my $indicator;
167 131 100 100     312 if ($left_ignorable && $right_ignorable) {
168 15 100       23 if (defined $next_indicator) {
169 13         13 $indicator = $next_indicator;
170             } else {
171 2         3 $indicator = "-";
172             }
173             } else {
174 116 100 100     365 if (!$left_ignorable && !$right_ignorable) {
175 103         125 $indicator = "|";
176             } else {
177 13 100       20 if ($left_ignorable) {
178             # insert a blank on the right and try again
179 6         13 splice(@match_chunks2, $i, 0, "");
180 6         6 $next_indicator = "<";
181 6         12 redo;
182             } else {
183             # insert a blank on the left and try again
184 7         13 splice(@match_chunks1, $i, 0, "");
185 7         9 $next_indicator = ">";
186 7         10 redo;
187             }
188             }
189             }
190              
191 118 50       494 $ret .= sprintf $match_format_str,
    50          
192             (defined $left_chunk ? $left_chunk : ""),
193             $indicator,
194             (defined $right_chunk ? $right_chunk : "");
195              
196 118         405 $next_indicator = undef;
197             }
198              
199 35         103 my @end_context1 = _get_end_context($options, $max1, $match->source1);
200 35         132 my @end_context2 = _get_end_context($options, $max2, $match->source2);
201              
202 35 50       77 my $max_end_context_len =
203             scalar(@end_context1) > scalar(@end_context2) ?
204             scalar(@end_context1) : scalar(@end_context2);
205              
206 35         77 for ($i = 0; $i < $max_end_context_len; $i++) {
207 70 100 100     246 if (defined $end_context1[$i] || defined $end_context2[$i]) {
208 43 100       226 $ret .= sprintf $context_format_str,
    100          
209             (defined $end_context1[$i] ? $end_context1[$i] : ""),
210             (defined $end_context2[$i] ? $end_context2[$i] : "");
211             }
212             }
213              
214              
215 35         185 return $ret;
216             }
217              
218             sub _get_match_chunks
219             {
220 100     100   153 my ($options, $min, $max, $source) = @_;
221              
222 100         146 my @ret = ();
223              
224 100         225 for (my $i = $min; $i <= $max; $i++) {
225 253         576 my $text = ($source->get_all_chunks)[$i];
226 253         510 $text =~ s/\t/ /g;
227 253         600 push @ret, $text;
228             }
229              
230 100         288 return @ret;
231             }
232              
233             sub _get_start_context
234             {
235 140     140   207 my ($options, $min, $source) = @_;
236              
237 140   50     494 my $context_chunks = $options->{context} || 3;
238 140         166 my $context_min = $min - $context_chunks;
239              
240 140         170 my @ret = ();
241              
242 140         380 for (my $i = $context_min; $i < $min; $i++) {
243 420 100       586 if ($i < 0) {
244 210         477 push @ret, undef;
245             } else {
246 210         516 my $text = ($source->get_all_chunks)[$i];
247 210         427 $text =~ s/\t/ /g;
248 210         660 push @ret, $text;
249             }
250             }
251              
252 140         368 return @ret;
253             }
254              
255             sub _get_end_context
256             {
257 140     140   190 my ($options, $max, $source) = @_;
258              
259 140   50     459 my $context_chunks = $options->{context} || 3;
260 140         180 my $context_max = $max + $context_chunks;
261              
262 140         182 my @ret = ();
263              
264 140         353 for (my $i = $max + 1; $i < $context_max; $i++) {
265 280 100       733 if ($i > $source->get_all_chunks_count - 1) {
266 142         364 push @ret, undef;
267             } else {
268 138         310 my $text = ($source->get_all_chunks)[$i];
269 138         266 $text =~ s/\t/ /g;
270 138         364 push @ret, $text;
271             }
272             }
273              
274 140         367 return @ret;
275             }
276              
277             sub _draw_match_vertically
278             {
279 35     35   53 my ($options, $match) = @_;
280              
281 35         112 my $ret = "match " . $match->as_string . "\n";
282              
283 35         114 $ret .= _draw_range_and_context($options, $match->min1, $match->max1,
284             $match->source1);
285 35         74 $ret .= "=== matches\n";
286              
287 35         111 $ret .= _draw_range_and_context($options, $match->min2, $match->max2,
288             $match->source2);
289              
290             }
291              
292             sub _draw_range_and_context
293             {
294 70     70   105 my ($options, $min, $max, $source) = @_;
295              
296 70         82 my $ret = "";
297              
298 70         148 for my $start_chunk_text (_get_start_context($options, $min, $source)) {
299 210 100       389 if (defined $start_chunk_text) {
300 105         207 $ret .= " $start_chunk_text\n";
301             }
302             }
303              
304 70         177 for (my $i = $min; $i <= $max; $i++) {
305 223         488 my $match_chunk_text = ($source->get_all_chunks)[$i];
306 223         6653 $ret .= "= $match_chunk_text\n";
307             }
308              
309 70         126 for my $end_chunk_text (_get_end_context($options, $max, $source)) {
310 140 100       270 if (defined $end_chunk_text) {
311 69         151 $ret .= " $end_chunk_text\n";
312             }
313             }
314              
315 70         306 return $ret;
316             }
317              
318             sub _get_term_width
319             {
320 35     35   41 my $options = shift;
321 35 50       69 if (defined $options->{term_width}) {
322 0         0 return $options->{term_width};
323             } else {
324 35         69 return 80;
325             }
326             }
327              
328             =head1 AUTHOR
329              
330             Kim Rutherford
331              
332             =head1 COPYRIGHT & LICENSE
333              
334             Copyright 2005,2006 Kim Rutherford. All rights reserved.
335              
336             This program is free software; you can redistribute it and/or modify it
337             under the same terms as Perl itself.
338              
339             =head1 DISCLAIMER
340              
341             This module is provided "as is" without warranty of any kind. It
342             may redistributed under the same conditions as Perl itself.
343              
344             =cut
345              
346              
347             1;