File Coverage

blib/lib/CGI/Wiki/Plugin/Diff.pm
Criterion Covered Total %
statement 21 105 20.0
branch 0 26 0.0
condition 0 22 0.0
subroutine 7 15 46.6
pod 7 7 100.0
total 35 175 20.0


line stmt bran cond sub pod time code
1             package CGI::Wiki::Plugin::Diff;
2              
3 1     1   10915 use strict;
  1         2  
  1         47  
4 1     1   5 use warnings;
  1         3  
  1         62  
5              
6             our $VERSION = '0.09';
7              
8 1     1   6 use base 'CGI::Wiki::Plugin';
  1         2  
  1         627  
9 1     1   1090 use Algorithm::Diff;
  1         11852  
  1         65  
10 1     1   1121 use VCS::Lite;
  1         6787  
  1         48  
11 1     1   1082 use Params::Validate::Dummy ();
  1         736  
  1         26  
12 1         7 use Module::Optional qw(Params::Validate
13 1     1   876 validate validate_pos SCALAR SCALARREF ARRAYREF HASHREF UNDEF);
  1         742  
14              
15             sub new {
16 0     0 1   my $class = shift;
17 0           my %par = validate( @_, {
18             metadata_separator => { type => SCALAR, default => "
\n"} ,
19             line_number_format => { type => SCALAR, default => "== Line \$_ ==\n" },
20             word_matcher => { type => SCALARREF, default => qr(
21             &.+?; #HTML special characters e.g. <
22             | #Line breaks
23             |\w+\s* #Word with trailing spaces
24             |. #Any other single character
25             )xsi },
26             } );
27 0           bless \%par, $class;
28             }
29              
30             sub differences {
31 0     0 1   my $self = shift;
32 0           my %args = validate( @_, {
33             node => { type => SCALAR},
34             left_version => { type => SCALAR},
35             right_version => { type => SCALAR},
36             meta_include => { type => ARRAYREF, optional => 1 },
37             meta_exclude => { type => ARRAYREF, optional => 1 } });
38              
39 0           my ($node, $v1, $v2) = @args{ qw( node left_version right_version) };
40 0           my $store = $self->datastore;
41 0           my $fmt = $self->formatter;
42            
43 0           my %ver1 = $store->retrieve_node( name => $node, version => $v1);
44 0           my %ver2 = $store->retrieve_node( name => $node, version => $v2);
45              
46 0           my $verstring1 = "Version ".$ver1{version};
47 0           my $verstring2 = "Version ".$ver2{version};
48            
49 0           my $el1 = VCS::Lite->new($verstring1,undef,
50             $self->content_escape($ver1{content}).
51             $self->{metadata_separator}.
52             $self->serialise_metadata($ver1{metadata},
53             @args{qw(meta_include meta_exclude)}));
54 0           my $el2 = VCS::Lite->new($verstring2,undef,
55             $self->content_escape($ver2{content}).
56             $self->{metadata_separator}.
57             $self->serialise_metadata($ver2{metadata},
58             @args{qw(meta_include meta_exclude)}));
59 0           my %pag = %ver1;
60 0           $pag{left_version} = $verstring1;
61 0           $pag{right_version} = $verstring2;
62 0           $pag{content} = $fmt->format($ver1{content});
63 0 0         my $dlt = $el1->delta($el2)
64             or return %pag;
65              
66 0           my @out;
67            
68 0           for ($dlt->hunks) {
69 0           my ($lin1,$lin2,$out1,$out2);
70 0           for (@$_) {
71 0           my ($ind,$line,$text) = @$_;
72 0 0         if ($ind ne '+') {
73 0   0       $lin1 ||= $line;
74 0           $out1 .= $text;
75             }
76 0 0         if ($ind ne '-') {
77 0   0       $lin2 ||= $line;
78 0           $out2 .= $text;
79             }
80             }
81 0           push @out,{ left => $self->line_number($lin1),
82             right => $self->line_number($lin2) };
83 0           my ($text1,$text2) = $self->intradiff($out1,$out2);
84 0           push @out,{left => $text1,
85             right => $text2};
86             }
87              
88 0           $pag{diff} = \@out;
89 0           %pag;
90             }
91              
92             sub line_number {
93 0     0 1   my $self = shift;
94              
95 0           local ($_) = validate_pos(@_, {type => SCALAR | UNDEF, optional => 1} );
96 0 0         return '' unless defined $_;
97              
98 0           my $fmt = '"'. $self->{line_number_format} . '"';
99 0           eval $fmt;
100             }
101              
102             sub serialise_metadata {
103 0     0 1   my $self = shift;
104 0           my ($all_meta,$include,$exclude) = validate_pos ( @_,
105             { type => HASHREF },
106             { type => ARRAYREF | UNDEF, optional => 1 },
107             { type => ARRAYREF | UNDEF, optional => 1 },
108             );
109 0   0       $include ||= [keys %$all_meta];
110 0   0       $exclude ||= [qw(comment username
111             __categories__checksum __locales__checksum)] ;
112            
113 0           my %metadata = map {$_,$all_meta->{$_}} @$include;
  0            
114 0           delete $metadata{$_} for @$exclude;
115              
116 0           join $self->{metadata_separator},
117 0           map {"$_='".join (',',sort @{$metadata{$_}})."'"}
  0            
118             sort keys %metadata;
119             }
120              
121             sub content_escape {
122 0     0 1   my $self = shift;
123 0           my ($str) = validate_pos( @_, { type => SCALAR } );
124              
125 0           $str =~ s/&/&/g;
126 0           $str =~ s/
127 0           $str =~ s/>/>/g;
128 0           $str =~ s!\s*?\n!
\n!gs;
129              
130 0           $str;
131             }
132              
133             sub intradiff {
134 0     0 1   my $self = shift;
135 0           my ($str1,$str2) = validate_pos( @_, {type => SCALAR|UNDEF },
136             {type => SCALAR|UNDEF });
137              
138 0 0         return (qq{$str1},"") unless $str2;
139 0 0         return ("",qq{$str2}) unless $str1;
140 0           my $re_wordmatcher = $self->{word_matcher};
141 0     0     my @diffs = Algorithm::Diff::sdiff([$str1 =~ /$re_wordmatcher/sg]
142 0           ,[$str2 =~ /$re_wordmatcher/sg], sub {$self->get_token(@_)});
143 0           my $out1 = '';
144 0           my $out2 = '';
145 0           my ($mode1,$mode2);
146              
147 0           for (@diffs) {
148 0           my ($ind,$c1,$c2) = @$_;
149              
150 0           my $newmode1 = $ind =~ /[c\-]/;
151 0           my $newmode2 = $ind =~ /[c+]/;
152 0 0 0       $out1 .= '' if $newmode1 && !$mode1;
153 0 0 0       $out2 .= '' if $newmode2 && !$mode2;
154 0 0 0       $out1 .= '' if !$newmode1 && $mode1;
155 0 0 0       $out2 .= '' if !$newmode2 && $mode2;
156 0           ($mode1,$mode2) = ($newmode1,$newmode2);
157 0           $out1 .= $c1;
158 0           $out2 .= $c2;
159             }
160 0 0         $out1 .= '' if $mode1;
161 0 0         $out2 .= '' if $mode2;
162              
163 0           ($out1,$out2);
164             }
165              
166             sub get_token {
167 0     0 1   my ($self,$str) = @_;
168              
169 0           $str =~ /^(\S*)\s*$/; # Match all but trailing whitespace
170              
171 0 0         $1 || $str;
172             }
173              
174             1;
175             __END__