Provided by: libsvn-notify-perl_2.84-2_all bug

Name

       SVN::Notify::Filter - Create output filters for SVN::Notify

Synopsis

         package SVN::Notify::Filter::Textile;
         use Text::Textile ();

         sub log_message {
             my ($notifier, $lines) = @_;
             return $lines unless $notify->content_type eq 'text/html';
             return [ Text::Textile->new->process( join $/, @$lines ) ];
         }

Description

       This document covers the output filtering capabilities of SVN::Notify. Output filters are
       simply subroutines that modify content before SVN::Notify outputs it. The idea is to
       provide a simple interface for anyone to use to change the format of the messages that
       SVN::Notify creates. Filters are loaded by the "filter" parameter to "new()" or by the
       "--filter" option to the "svnnotify" command-line program.

   A Quick Example
       The most common use for an output filter is to modify the format of log commit messages.
       Say that your developers write their commit messages in Markdown format, and you'd like it
       to be reformatted as HTML in the messages sent by SVN::Notify::HTML. To do so, just create
       a Perl module and put it somewhere in the Perl path. Something like this:

         package SVN::Notify::Filter::Markdown;
         use strict;
         use Text::Markdown ();

         sub log_message {
             my ($notifier, $lines) = @_;
             return $lines unless $notify->content_type eq 'text/html';
             return [ Text::Markdown->new->markdown( join $/, @$lines ) ];
         }

       Put this code in a file named SVN/Notify/Filter/Markdown.pm somewhere in your Perl's path.
       The way that SVN::Notify filters work is that you simply define a subroutine named for
       what you want to filter. The subroutine's first argument will always be the SVN::Notify
       object that's generating the notification message, and the second argument will always be
       the content to be filtered.

       In this example, we wanted to filter the commit log message, so we just defined a
       subroutine named "log_message()" and, if the message will be HTML, passed the lines of the
       commit message to Text::Markdown to format, returning a new array reference. And that's
       all there is to writing SVN::Notify filters: Define a subroutine, process the second
       argument, and return a data structure in the same format as that argument (usually an
       array reference).

       Now, to use this filter, just use the "--filter" option:

         svnnotify -p "$1" -r "$2" --handler HTML --filter Markdown

       SVN::Notify will assume that a filter option without "::" is in the SVN::Notify::Filter
       name space, and will load it accordingly. If you instead created your filter in some other
       name space, say "My::Filter::Markdown", then you'd specify the full package name in the
       "--filter" option:

         svnnotify -p "$1" -r "$2" --handler HTML --filter My::Filter::Markdown

       And that's it! The filter modifies the contents of the log message before
       SVN::Notify::HTML spits it out.

   The Details
       Writing SVN::Notify filters is easy. The name of each subroutine in a filter module
       determines what content it filters. The filter subroutines take two arguments: the
       SVN::Notify object that's creating the notification message, and the content to be
       filtered. They should return the filtered content in the same data structure as that in
       which it was passed. This makes it easy to change the output of SVN::Notify without the
       hassle of subclassing or sending patches to the maintainer.

       The names of the filter subroutines and the types of their content arguments and return
       values are as follows, in the order in which they execute:

         Sub Name     | Second Argument
         -------------+---------------------------------------------------------------
         pre_prepare  | undef
         recipients   | Array reference of email addresses.
         from         | String with sender address.
         subject      | String with the subject line.
         post_prepare | undef
         pre_execute  | undef
         headers      | Array reference of individual email headers lines.
         start_html   | An array of lines starting an SVN::Notify::HTML document.
         css          | An array of lines of CSS. Used only by SVN::Notify::HTML.
         start_body   | Array reference of lines at the start of the message body.
         metadata     | Array reference of lines of the metadata part of the message.
         log_message  | Array reference of lines of log message.
         file_lists   | Array reference of lines of file names. The first line will
                      | be the type of change for the list, the next a simple line of
                      | dashes, and each of the rest of the lines a file name.

         diff         | A file handle reference to the diff.
         end_body     | Array reference of lines at the end of the message body.
         post_execute | undef

       Note that the data passed to the filters by SVN::Notify subclasses (SVN::Notify::HTML and
       SVN::Notify::HTML::ColorDiff) may be in a slightly different format than documented here.
       Consult the documentation for the relevant methods in those classes for details.

       There are four special filter subroutines that are called at the beginning and at the end
       of the execution of the "prepare()" and "execute()" methods, named "pre_prepare",
       "post_prepare", "pre_execute", and "post_execute". No data is passed to them and their
       return values are ignored, but they are included to enable callbacks at the points at
       which they execute. If, for example, you wanted to set the value of the "to" attribute
       before SVN::Notify checks to make sure that there are recipients to whom to send an email,
       you'd want to do so in a "pre_prepare" filter.

       The package name of the filter module can be anything you like; just pass it via the
       "filter" parameter, e.g., "filter => [ 'My::Filter' ]" (or "--filter My::Filter" on the
       command-line). If, however, it's in the "SVN::Notify::Filter" name space, you can just
       pass the last bit as the filter name, for example "filter => [ 'NoSpam' ]" (or "--filter
       NoSpam" on the command-line) for "SVN::Notify::Filter::NoSpam".

       The first argument to a filter subroutine is always the SVN::Notify object that's
       generating the message to be delivered. This is so that you can access its attributes for
       your own nefarious purposes in the filter, as in the first example below.

       But more importantly -- and more hackerously -- you can add attributes to the SVN::Notify
       class from your filters. Just call "SVN::Notify->register_attributes" to do so, as in the
       final example below. below.

   Examples
       First, see the "Synopsis" for an example that converts Textile-formatted log messages to
       HTML, and "A Quick Example" for a filter that converts a Markdown-formatted log message to
       HTML. If you format your log messages for Trac, just use the included
       SVN::Notify::Filter::Trac filter. There is also SVN::Notify::Filter::Markdown on CPAN, and
       maybe other filters as well.

       But if you can't find anything that does what you want, here are some examples to get you
       started writing your own filters:

       •   Map committers to senders

           Map committer user names to email addresses using a lookup table. The "from" filter
           gets and returns a string representing the sender. Note how this example makes use of
           the SVN::Notify object to get the username of the committer:

             package SVN::Notify::Filter::FromTable;
             my %committers = (
                 'homer' => 'homer@simpson.com',
                 'bart'  => 'bart@gmail.com',
                 'marge' => 'marge@urbanmamas.com',
             );
             sub from {
                 my ($notifier, $from) = @_;
                 return $committers{ $notifier->user } || $from;
             }

       •   Add a recipient

           Easily done from the command-line using "--to", but hey, why not just filter it?

             package SVN::Notify::Filter::Cc;
             sub recipients {
                 my ($notifier, $recip) = @_;
                 push @$recip, 'boss@example.com';
                 return $recip;
             }

       •   Clean up the subject

           Need to keep the subject line clean? Just modify the string and return it:

             package SVN::Notify::Filter::FromTable;
             my $nasties = qr/\b(?:golly|shucks|darn)\b/i;
             sub subject {
                 my ($notifier, $subject) = @_;
                 $subject =~ s/$nasties/[elided]/g;
                 return $subject;
             }

       •   Add an extra header

           This emulates "add_header" to demonstrate header filtering. Maybe you have a special
           header that tells your spam filtering service to skip filtering for certain messages
           (not a good idea, but what the hell?):

             package SVN::Notify::Filter::NoSpam;
             sub headers {
                 my ($notifier, $headers) = @_;
                 push @$headers, 'X-NotSpam: true';
                 return $headers;
             }

       •   Uppercase meta data labels

           Change the format of the commit meta data section of the message to uppercase all of
           the headers, so that "Revision: 111" becomes "REVISION: 111":

           Note that this example also makes use of the "content_type()" method of SVN::Notify to
           determine whether or not to actually do the filtering. This prevents it from being
           applied to HTML messages, where it likely wouldn't be able to do much.

             package SVN::Notify::Filter::UpLabels;
             sub metadata {
                 my ($notifier, $lines) = @_;
                 return $lines unless $notify->content_type eq 'text/plain';
                 s/([^:]+:)/uc $1/eg for @$lines;
                 return $lines;
             }

       •   Wrap your log message

           Log message filtering will probably be quite common, generally to reformat it (see,
           for example, the included SVN::Notify::Filter::Trac filter, as well as
           SVN::Notify::Filter::Markdown on CPAN). If the Markdown and Textile examples above are
           more than you need, here's a simple filter that reformats the log message so that
           paragraphs are wrapped.

             package SVN::Notify::Filter::WrapMessage;
             use Text::Wrap ();
             sub log_message {
                 my ($notifier, $lines) = @_;
                 return [ Text::Wrap::wrap( '', '', @$lines ) ];
             }

       •   Remove leading "trunk/" from file names

           Just to demonstrate how to filter file lists:

             package SVN::Notify::Filter::StripTrunk;
             sub file_lists {
                 my ($notifier, $lines) = @_;
                 s{^(\s*)trunk/}{$1} for @$lines;
                 return $lines;
             }

       •   Remove leading "trunk/" from file names in a diff

           This one is a little more complicated because diff filters need to return a file
           handle. SVN::Notify tries to be as efficient with resources as it can, so it reads
           each line of the diff from the file handle one-at-a-time, processing and outputting
           each in turn so as to avoid loading the entire diff into memory. To retain this
           pattern, the best approach is to tie the file handle to a class that does the
           filtering one line at a time. The requisite "tie" class needs only three methods:
           "TIEHANDLE" "READLINE", and "CLOSE". In this example, I've defined them in a different
           name space than the filter subroutine, so as to simplify SVN::Notify's loading of
           filters and to keep thing neatly packaged. Note that this filter is applied before
           SVN::Notify::HTML outputs its diff, so you can modify things before they get marked up
           by, say, SVN::Notify::HTML::ColorDiff.

             package My::IO::TrunkStripper;
             sub TIEHANDLE {
                 my ($class, $fh) = @_;
                 bless { fh => $fh }, $class;
             }

             sub READLINE {
                 my $fh = shift->{fh};
                 defined( my $line = <$fh> ) or return;
                 $line =~ s{^((?:-{3}|[+]{3})\s+)trunk/}{$1};
                 return $line;
             }

             sub CLOSE {
                 close shift->{fh} or die $! ? "Error closing diff pipe: $!"
                                             : "Exit status $? from diff pipe";
             }

             package SVN::Notify::Filter::StripTrunkDiff;
             use Symbol ();

             sub diff {
                 my ($notifier, $fh) = @_;
                 my $filter = Symbol::gensym;
                 tie *{ $filter }, 'My::IO::TrunkStripper', $fh;
                 return $filter;
             }

           However, if you don't mind loading the entire diff into memory (because you just know
           that all of your commits are small [you know that's not true, right?]), you can
           simplify things by using a data structure and an existing IO module to do the same
           thing:

             package SVN::Notify::Filter::StripTrunkDiff;
             use IO::ScalarArray;

             sub diff {
                 my ($notifier, $fh) = @_;
                 my @lines;
                 while (<$fh>) {
                     s{^((?:-{3}|[+]{3})\s+)trunk/}{$1};
                     push @lines, $_;
                 }
                 return IO::ScalarArray->new(\@lines);
             }

           But do beware of this approach if you're likely to commit changes that would generate
           very larges diffs!

       •   Filter based on parameter

           You can also add attributes (and therefor command-line options) to SVN::Notify in your
           filter in order to alter its behavior. This is precisely what the included
           SVN::Notify::Filter::Trac module does: it adds a new attribute, "trac_url()", so that
           it can create the proper Trac links in your commit messages. See the documentation for
           register_attributes() for details on its arguments mapping attribute names to
           Getopt::Long rules.

           Note that this example also makes use of the "content_type()" method of SVN::Notify to
           determine whether or not to actually do the filtering. This prevents it from
           inadvertently converting the log file to HTML in plain text messages, such as those
           sent by default by SVN::Notify, or the plain text part sent by
           SVN::Notify::Alternative.

             package SVN::Notify::Filter::Trac;

             SVN::Notify->register_attributes( trac_url => 'trac-url=s' );

             sub log_message {
                 my ($notify, $lines) = @_;
                 return $lines unless $notify->content_type eq 'text/html';
                 my $trac = Text::Trac->new( trac_url => $notify->trac_url );
                 return [ $trac->parse( join $/, @{ $lines } ) ];
             }

Contributing Filters

       I created the filtering feature of SVN::Notify in the hopes that all those folks who want
       new features for SVN::Notify will stop asking me for them and instead start writing them
       themselves. I should have thought to do it a long time ago, because, in truth, about half
       the features of SVN::Notify could have been implemented as filters (maybe more than half).

       So by all means write your filters for SVN::Notify. If you've think you've got a really
       good one, or a filter that others will find useful, please do not send it to me. A better
       option is to package it up and put it on the CPAN. Go ahead! Take an example from this
       document, if you want, put it in a module, write a few tests, and upload the distribution.
       Model your distribution on SVN::Notify::Filter::Markdown, which is already separately
       distributed on CPAN. Let's create a mini-ecosystem of SVN::Notify filters, all available
       via CPAN. That way, lots of people can take advantage of them, new "features" can be added
       on a regular basis, and I don't have to keep adding cruft to SVN::Notify itself!

See Also

       SVN::Notify
           The class that makes this stuff all work.

       SVN::Notify::HTML
           The SVN::Notify class that likely will be most often used when filtering messages.
           Check its documentation for variations on filter handling from SVN::Notify.

       SVN::Notify::Filter::Trac
           Filters log messages to convert them from Trac wiki format to HTML. Also demonstrates
           the ability to add attributes to SVN::Notify (and options to svnnotify for added
           functionality of the filter.

       SVN::Notify::Filter::Markdown
           A separate CPAN distribution that filters log messages to convert them from Markdown
           format to HTML. Check it out to get an idea how to create your own filter
           distributions on CPAN.

Author

       David E. Wheeler <david@justatheory.com>

Copyright and License

       Copyright (c) 2008-2011 David E. Wheeler. Some Rights Reserved.

       This module is free software; you can redistribute it and/or modify it under the same
       terms as Perl itself.