1package SVN::Notify::HTML::ColorDiff; 2 3use strict; 4use HTML::Entities; 5use SVN::Notify::HTML (); 6 7$SVN::Notify::HTML::ColorDiff::VERSION = '2.87'; 8@SVN::Notify::HTML::ColorDiff::ISA = qw(SVN::Notify::HTML); 9 10=head1 Name 11 12SVN::Notify::HTML::ColorDiff - Subversion activity HTML notification with colorized diff 13 14=head1 Synopsis 15 16Use F<svnnotify> in F<post-commit>: 17 18 svnnotify --repos-path "$1" --revision "$2" \ 19 --to developers@example.com --handler HTML::ColorDiff [options] 20 21Use the class in a custom script: 22 23 use SVN::Notify::HTML::ColorDiff; 24 25 my $notifier = SVN::Notify::HTML::ColorDiff->new(%params); 26 $notifier->prepare; 27 $notifier->execute; 28 29=head1 Description 30 31This subclass of L<SVN::Notify::HTML|SVN::Notify::HTML> sends HTML formatted 32email messages for Subversion activity, and if the C<with_diff> parameter is 33specified (but not C<attach_diff>), then a pretty colorized version of the 34diff will be included, rather than the plain text diff output by 35SVN::Notify::HTML. 36 37=head1 Usage 38 39To use SVN::Notify::HTML::ColorDiff, simply follow the 40L<instructions|SVN::Notify/Usage> in SVN::Notify, but when using F<svnnotify>, 41specify C<--handler HTML::ColorDiff>. 42 43=cut 44 45############################################################################## 46 47=head1 Instance Interface 48 49=head2 Instance Methods 50 51=head3 output_css 52 53 $notifier->output_css($file_handle); 54 55This method starts outputs the CSS for the HTML message. 56SVN::Notify::HTML::ColorDiff adds extra CSS to its output so that it can 57nicely style the diff. 58 59=cut 60 61# We use _css() so that ColorDiff can override it and the filters then applied 62# only one to all of the CSS. 63 64############################################################################## 65 66=head3 output_diff 67 68 $notifier->output_diff($out_file_handle, $diff_file_handle); 69 70Reads the diff data from C<$diff_file_handle> and prints it to 71C<$out_file_handle> for inclusion in the notification message. The diff is 72output with nice colorized HTML markup. Each line of the diff file is escaped 73by C<HTML::Entities::encode_entities()>. 74 75If there are any C<diff> filters, this method will do no HTML formatting, but 76redispatch to L<SVN::Notify::output_diff|SVN::Notify/"output_diff">. See 77L<Writing Output Filters|SVN::Notify/"Writing Output Filters"> for details on 78filters. 79 80=cut 81 82my %types = ( 83 Modified => 'modfile', 84 Added => 'addfile', 85 Deleted => 'delfile', 86 Copied => 'copfile', 87); 88 89sub output_diff { 90 my ($self, $out, $diff) = @_; 91 if ( $self->filters_for('diff') ) { 92 return $self->SUPER::output_diff($out, $diff); 93 } 94 $self->_dbpnt( "Outputting colorized HTML diff") if $self->verbose > 1; 95 96 my $in_div; 97 my $in_span = ''; 98 print $out qq{</div>\n<div id="patch">\n<h3>Diff</h3>\n}; 99 my ($length, %seen) = 0; 100 my $max = $self->max_diff_length; 101 102 while (my $line = <$diff>) { 103 $line =~ s/[\n\r]+$//; 104 next unless $line; 105 if ( $max && ( $length += length $line ) >= $max ) { 106 print $out "</$in_span>" if $in_span; 107 print $out qq{<span class="lines">\@\@ Diff output truncated at $max characters. \@\@\n</span>}; 108 $in_span = ''; 109 last; 110 } else { 111 if ($line =~ /^(Modified|Added|Deleted|Copied): (.*)/) { 112 my $class = $types{my $action = $1}; 113 ++$seen{$2}; 114 my $file = encode_entities($2, '<>&"'); 115 (my $id = $file) =~ s/[^\w_]//g; 116 117 print $out "</$in_span>" if $in_span; 118 print $out "</span></pre></div>\n" if $in_div; 119 120 # Dump line, but check it's content. 121 if (<$diff> !~ /^=/) { 122 # Looks like they used --no-diff-added or --no-diff-deleted. 123 ($in_span, $in_div) = ''; 124 print $out qq{<a id="$id"></a>\n<div class="$class">}, 125 qq{<h4>$action: $file</h4></div>\n}; 126 next; 127 } 128 129 # Get the revision numbers. 130 my $before = <$diff>; 131 $before =~ s/[\n\r]+$//; 132 133 if ($before =~ /^\(Binary files differ\)/) { 134 # Just output the whole file div. 135 print $out qq{<a id="$id"></a>\n<div class="binary"><h4>}, 136 qq{$action: $file</h4>\n<pre class="diff"><span>\n}, 137 qq{<span class="cx">$before\n</span></span></pre></div>\n}; 138 ($in_span, $in_div) = ''; 139 next; 140 } 141 142 my ($rev1) = $before =~ /\(rev (\d+)\)$/; 143 my $after = <$diff>; 144 $after =~ s/[\n\r]+$//; 145 my ($rev2) = $after =~ /\(rev (\d+)\)$/; 146 147 # Output the headers. 148 print $out qq{<a id="$id"></a>\n<div class="$class"><h4>$action: $file}, 149 " ($rev1 => $rev2)</h4>\n"; 150 print $out qq{<pre class="diff"><span>\n<span class="info">}; 151 $in_div = 1; 152 print $out encode_entities($_, '<>&"'), "\n" for ($before, $after); 153 print $out "</span>"; 154 $in_span = ''; 155 } elsif ($line =~ /^Property changes on: (.*)/ && !$seen{$1}) { 156 # It's just property changes. 157 my $file = encode_entities($1, '<>&"'); 158 (my $id = $file) =~ s/[^\w_]//g; 159 # Dump line. 160 <$diff>; 161 162 # Output the headers. 163 print $out "</$in_span>" if $in_span; 164 print $out "</span></pre></div>\n" if $in_div; 165 print $out qq{<a id="$id"></a>\n<div class="propset">}, 166 qq{<h4>Property changes: $file</h4>\n<pre class="diff"><span>\n}; 167 $in_div = 1; 168 $in_span = ''; 169 } elsif ($line =~ /^\@\@/) { 170 print $out "</$in_span>" if $in_span; 171 print $out ( 172 qq{<span class="lines">}, 173 encode_entities($line, '<>&"'), 174 "\n</span>", 175 ); 176 $in_span = ''; 177 } elsif ($line =~ /^([-+])/) { 178 my $type = $1 eq '+' ? 'ins' : 'del'; 179 if ($in_span eq $type) { 180 print $out encode_entities($line, '<>&"'), "\n"; 181 } else { 182 print $out "</$in_span>" if $in_span; 183 print $out ( 184 qq{<$type>}, 185 encode_entities($line, '<>&"'), 186 "\n", 187 ); 188 $in_span = $type; 189 } 190 } else { 191 if ($in_span eq 'cx') { 192 print $out encode_entities($line, '<>&"'), "\n"; 193 } else { 194 print $out "</$in_span>" if $in_span; 195 print $out ( 196 qq{<span class="cx">}, 197 encode_entities($line, '<>&"'), 198 "\n", 199 ); 200 $in_span = 'span'; 201 } 202 } 203 } 204 } 205 print $out "</$in_span>" if $in_span; 206 print $out "</span></pre>\n</div>\n" if $in_div; 207 print $out "</div>\n"; 208 209 close $diff or warn "Child process exited: $?\n"; 210 return $self; 211} 212 213############################################################################## 214 215sub _css { 216 my $css = shift->SUPER::_css; 217 push @$css, 218 qq(#patch h4 {font-family: verdana,arial,helvetica,sans-serif;), 219 qq(font-size:10pt;padding:8px;background:#369;color:#fff;), 220 qq(margin:0;}\n), 221 qq(#patch .propset h4, #patch .binary h4 {margin:0;}\n), 222 qq(#patch pre {padding:0;line-height:1.2em;margin:0;}\n), 223 qq(#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;), 224 qq(overflow:auto;}\n), 225 qq(#patch .propset .diff, #patch .binary .diff {padding:10px 0;}\n), 226 qq(#patch span {display:block;padding:0 10px;}\n), 227 qq(#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, ), 228 qq(#patch .binary, #patch .copfile {border:1px solid #ccc;), 229 qq(margin:10px 0;}\n), 230 qq(#patch ins {background:#dfd;text-decoration:none;display:block;), 231 qq(padding:0 10px;}\n), 232 qq(#patch del {background:#fdd;text-decoration:none;display:block;), 233 qq(padding:0 10px;}\n), 234 qq(#patch .lines, .info {color:#888;background:#fff;}\n); 235 return $css; 236} 237 2381; 239__END__ 240 241=head1 See Also 242 243=over 244 245=item L<SVN::Notify|SVN::Notify> 246 247=item L<SVN::Notify::HTML|SVN::Notify::HTML> 248 249=item L<CVSspam|http://www.badgers-in-foil.co.uk/projects/cvsspam/> 250 251=back 252 253=head1 To Do 254 255=over 256 257=item * 258 259Add inline emphasis just on the text that changed between two lines, like 260this: L<http://www.badgers-in-foil.co.uk/projects/cvsspam/example.html>. 261 262=item * 263 264Add links to To Do stuff to the top of the email, as pulled in from the diff. 265This might be tricky, since the diff is currently output I<after> the message 266body. Maybe use absolute positioning CSS? 267 268=back 269 270=head1 271 272=head1 Author 273 274David E. Wheeler <david@justatheory.com> 275 276=head1 Copyright and License 277 278Copyright (c) 2004-2018 David E. Wheeler. Some Rights Reserved. 279 280This module is free software; you can redistribute it and/or modify it under 281the same terms as Perl itself. 282 283=cut 284