blib/lib/Wiki/Toolkit/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 Wiki::Toolkit::Plugin::Diff; | ||||||
2 | |||||||
3 | 2 | 2 | 12883 | use strict; | |||
2 | 6 | ||||||
2 | 79 | ||||||
4 | 2 | 2 | 13 | use warnings; | |||
2 | 4 | ||||||
2 | 105 | ||||||
5 | |||||||
6 | our $VERSION = '0.12'; | ||||||
7 | |||||||
8 | 2 | 2 | 11 | use base 'Wiki::Toolkit::Plugin'; | |||
2 | 13 | ||||||
2 | 1752 | ||||||
9 | 2 | 2 | 1701 | use Algorithm::Diff; | |||
2 | 5798 | ||||||
2 | 124 | ||||||
10 | 2 | 2 | 1196 | use VCS::Lite; | |||
2 | 6784 | ||||||
2 | 54 | ||||||
11 | 2 | 2 | 2149 | use Params::Validate::Dummy (); | |||
2 | 2005 | ||||||
2 | 60 | ||||||
12 | 2 | 14 | use Module::Optional qw(Params::Validate | ||||
13 | 2 | 2 | 1619 | validate validate_pos SCALAR SCALARREF ARRAYREF HASHREF UNDEF); | |||
2 | 1425 | ||||||
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/</g; | |||||
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__ |