| 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__ |