File Coverage

blib/lib/SVN/Notify.pm
Criterion Covered Total %
statement 464 493 94.1
branch 229 294 77.8
condition 76 112 67.8
subroutine 54 57 94.7
pod 30 30 100.0
total 853 986 86.5


line stmt bran cond sub pod time code
1             package SVN::Notify;
2              
3 293     293   254933 use strict;
  293         1134  
  293         11126  
4             require 5.006_000;
5 293     293   1396 use constant WIN32 => $^O eq 'MSWin32';
  293         376  
  293         24420  
6 293     293   2080 use constant PERL58 => $] > 5.007_000;
  293         543  
  293         117482  
7             require Encode if PERL58;
8             $SVN::Notify::VERSION = '2.87';
9              
10             # Make sure any output (such as from _dbpnt()) triggers no Perl warnings.
11             if (PERL58) {
12             # Dupe them?
13             binmode STDOUT, ':utf8';
14             binmode STDERR, ':utf8';
15             }
16              
17             =head1 Name
18              
19             SVN::Notify - Subversion activity notification
20              
21             =head1 Synopsis
22              
23             Use F in F:
24              
25             svnnotify --repos-path "$1" --revision "$2" \
26             --to developers@example.com [options]
27              
28             svnnotify --repos-path "$1" --revision "$2" \
29             --to-cx-regex i10n@example.com=I10N [options]
30              
31             Use the class in a custom script:
32              
33             use SVN::Notify;
34              
35             my $notifier = SVN::Notify->new(%params);
36             $notifier->prepare;
37             $notifier->execute;
38              
39             =head1 Description
40              
41             This class may be used for sending email messages for Subversion repository
42             activity. There are a number of different modes supported, and SVN::Notify is
43             fully subclassable, to add new functionality, and offers L
44             content filtering|SVN::Notify::Filter> to easily modify the format of its
45             messages. By default, A list of all the files affected by the commit will be
46             assembled and listed in a single message. An additional option allows diffs to
47             be calculated for the changes and either appended to the message or added as
48             an attachment. See the C and C options below.
49              
50             =head1 Usage
51              
52             To use SVN::Notify, simply add a call to F to your Subversion
53             repository's F script. This script lives in the F
54             directory at the root of the repository directory; consult the documentation
55             in F for details. Make sure that you specify the complete
56             path to F, as well as to F and F in the options
57             passed to F so that everything executes properly. And if you
58             specify any string options, be sure that they are in the encoding specified by
59             the C<--encoding> option, or UTF-8 if you have not specified C<--encoding>.
60              
61             =head2 Windows Usage
62              
63             To get SVN::Notify to work properly in a F script, you must set
64             the following environment variables, as they will likely not be present inside
65             Apache:
66              
67             =over
68              
69             =item PATH=C:\perl\bin
70              
71             =item OS=Windows_NT
72              
73             =item SystemRoot=C:\WINDOWS
74              
75             =back
76              
77             See L
78             HOWTO|http://svn.haxx.se/users/archive-2006-05/0593.shtml> for more detailed
79             information on getting SVN::Notify running on Windows. If you have issues with
80             asynchronous execution, try using
81             L|http://www.koders.com/csharp/fidE2724F44EF2D47F1C0FE76C538006435FA20051D.aspx>
82             to run F.
83              
84             =cut
85              
86             # Map the svnlook changed codes to nice labels.
87             my %map = (
88             U => 'Modified Paths',
89             A => 'Added Paths',
90             D => 'Removed Paths',
91             _ => 'Property Changed',
92             );
93              
94             my %filters;
95              
96             ##############################################################################
97              
98             =head1 Class Interface
99              
100             =head2 Constructor
101              
102             =head3 new
103              
104             my $notifier = SVN::Notify->new(%params);
105              
106             Constructs and returns a new SVN::Notify object. This object is a handle on
107             the whole process of collecting meta data and content for the commit email and
108             then sending it. As such, it takes a number of parameters to affect that
109             process.
110              
111             Each of these parameters has a corresponding command-line option that can be
112             passed to F. The options have the same names as these parameters,
113             but any underscores you see here should be replaced with dashes when passed to
114             F. Most also have a corresponding single-character option. On Perl
115             5.8 and higher, If you pass parameters to C, they B be L
116             into Perl's internal form|Encode/"PERL ENCODING API"> if they have any
117             non-ASCII characters.
118              
119             Supported parameters:
120              
121             =over
122              
123             =item repos_path
124              
125             svnnotify --repos-path "$PATH"
126             svnnotify -p "$PATH"
127              
128             The path to the Subversion repository. The path is passed as the first
129             argument when Subversion executes F. So you can simply pass C<$1>
130             to this parameter if you like. See the documentation in F for
131             details. Required.
132              
133             =item revision
134              
135             svnnotify --revision "$REV"
136             svnnotify -r "$REV"
137              
138             The revision number for the current commit. The revision number is passed as
139             the second argument when Subversion executes F. So you can
140             simply pass C<$2> to this parameter if you like. See the documentation in
141             F for details. Required.
142              
143             =item to
144              
145             svnnotify --to commiters@example.com
146             svnnotify -t commiters@example.com --to managers@example.com
147              
148             The address or addresses to which to send the notification email. Can be used
149             multiple times to specify multiple addresses. This parameter is required
150             unless either C or C is specified.
151              
152             =item to_regex_map
153              
154             svnnotify --to-regex-map translate@example.com=L18N \
155             -x legal@example.com=License
156              
157             This parameter specifies a hash reference of email addresses to regular
158             expression strings. SVN::Notify will compile the regular expression strings
159             into regular expression objects, and then send notification messages if and
160             only if the name of one or more of the paths affected by a commit matches the
161             regular expression. This is a good way to have a notification email sent to a
162             particular mail address (or comma-delimited list of addresses) only for
163             certain parts of the subversion tree. This parameter is required unless C
164             or C is specified.
165              
166             The command-line options, C<--to-regex_map> and C<-x>, can be specified any
167             number of times, once for each entry in the hash to be passed to C. The
168             value passed to the option must be in the form of the key and the value
169             separated by an equal sign. Consult the L documentation for more
170             information.
171              
172             Here's an example complements of Matt Doar of how to use C to do
173             per-branch matching:
174              
175             author=`svnlook author $REPOS -r $REV`
176              
177             # The mail regexes should match all the top-level directories
178             /usr/bin/svnnotify --repos-path "$REPOS" --revision "$REV" \
179             -x eng-bar@example.com,${EXTRAS}="^Bar" \
180             -x eng-foo@example.com,${EXTRAS}="^trunk/Foo|^branches/Foo|^tags/Foo" \
181             -x $author@example.com="^users" --subject-cx
182              
183             =item to_email_map
184              
185             svnnotify --to-email-map L18N=translate@example.com \
186             --to-email-map License=legal@example.com
187              
188             The inverse of C: The regular expression is the hash key
189             and the email address or addresses are the value.
190              
191             =item from
192              
193             svnnotify --from somewhere@example.com
194             svnnotify -f elsewhere@example.com
195              
196             The email address to use in the "From" line of the email. If not specified,
197             SVN::Notify will use the username from the commit, as returned by C
198             info>.
199              
200             =item user_domain
201              
202             svnnotify --user-domain example.com
203             svnnotify -D example.net
204              
205             A domain name to append to the username for the "From" header of the email.
206             During a Subversion commit, the username returned by C is
207             usually something like a Unix login name. SVN::Notify will use this username
208             in the email "From" header unless the C parameter is specified. If you
209             wish to have the username take the form of a real email address, specify a
210             domain name and SVN::Notify will append C<\@$domain_name> to the username in
211             order to create a real email address. This can be useful if all of your
212             committers have an email address that corresponds to their username at the
213             domain specified by the C parameter.
214              
215             =item svnlook
216              
217             svnnotify --svnlook /path/to/svnlook
218             svnnotify -l /path/to/svnlook
219              
220             The location of the F executable. If not specified, SVN::Notify will
221             search through the directories in the C<$PATH> environment variable, plus in
222             F and F, for an F executable. Specify a
223             full path to F via this option or by setting the C<$SVNLOOK>
224             environment variable if F isn't in your path or to avoid loading
225             L.
226              
227             It's important to provide a complete path to F because the
228             environment during the execution of F is anemic, with nary a
229             C<$PATH> environment variable to be found. So if F appears not to
230             be working at all (and Subversion seems loathe to log when it dies!), make
231             sure that you have specified the complete path to a working F
232             executable.
233              
234             =item sendmail
235              
236             svnnotify --sendmail /path/to/sendmail
237             svnnotify -s /path/to/sendmail
238              
239             The location of the F executable. If neither the C nor the
240             C parameter is specified, SVN::Notify will search through the
241             directories in the C<$PATH> environment variable, plus in F
242             and F, for an F executable. Specify a full path to
243             F via this option or by setting the C<$SENDMAIL> environment
244             variable if F isn't in your path or to avoid loading
245             L. The same caveats as applied to the location of the
246             F executable apply here.
247              
248             =item set_sender
249              
250             svnnotify --set-sender
251             svnnotify -E
252              
253             Uses the C<-f> option to C to set the envelope sender address of the
254             email to the same address as is used for the "From" header. If you're also
255             using the C option, be sure to make it B an email address. Don't
256             include any other junk in it, like a sender's name. Ignored when using
257             C.
258              
259             =item smtp
260              
261             svnnotify --smtp smtp.example.com
262              
263             The address for an SMTP server through which to send the notification email.
264             If unspecified, SVN::Notify will use F to send the message. If
265             F is not installed locally (such as on Windows boxes!), you I
266             specify an SMTP server.
267              
268             =item smtp_tls
269              
270             svnnotify --smtp-tls
271              
272             Use TLS authentication and encrypted channels for connecting with the server.
273             Usually, TLS servers will require user/password authentication.
274              
275             =item smtp_user
276              
277             svnnotify --smtp-user myuser
278              
279             The user name for SMTP authentication. If this option is specified,
280             SVN::Notify will use L to send the notification
281             message, and will of course authenticate to the SMTP server.
282              
283             =item smtp_pass
284              
285             svnnotify --smtp-pass mypassword
286              
287             The password for SMTP authentication. Use in parallel with C.
288              
289             =item smtp_port
290              
291             svnnotify --smtp-port 465
292              
293             The port for an SMTP server through which to send the notification email. The
294             default port is 25.
295              
296             =item smtp_authtype
297              
298             svnnotify --smtp-authtype authtype
299              
300             Deprecated in SVN::Notify 2.83, where it has become a no-op. The auth type is
301             determined by the contents returned by the SMTP server's response to the
302             C command. See L for details.
303              
304             =item encoding
305              
306             svnnotify --encoding UTF-8
307             svnnotify -c Big5
308              
309             The character set typically used on the repository for log messages, file
310             names, and file contents. Used to specify the character set in the email
311             Content-Type headers and, when the C parameter is specified, the
312             C<$LANG> environment variable when launching C. See L
313             Encoding Support"> for more information. Defaults to "UTF-8".
314              
315             =item charset
316              
317             svnnotify --charset UTF-8
318              
319             Deprecated. Use C instead.
320              
321             =item svn_encoding
322              
323             svnnotify --svn-encoding euc-jp
324              
325             The character set used in files and log messages managed in Subversion. It's
326             useful to set this option if you store files in Subversion using one character
327             set but want to send notification messages in a different character set.
328             Therefore C would be used for the notification message, and
329             C would be used to read in data from Subversion. See
330             L for more information. Defaults to the value
331             stored in C.
332              
333             =item diff_encoding
334              
335             svnnotify --diff-encoding iso-2022-jp
336              
337             The character set used by files in Subversion, and thus present in the the
338             diff. It's useful to set this option if you store files in Subversion using
339             one character write log messages in a different character set. Therefore
340             C would be used to read the log message and C
341             would be used to read the diff from Subversion. See L
342             Support"> for more information. Defaults to the value stored in
343             C.
344              
345             =item language
346              
347             svnnotify --language fr
348             svnnotify -g i-klingon
349              
350             The language typically used on the repository for log messages, file names,
351             and file contents. Used to specify the email Content-Language header and to
352             set the C<$LANG> environment variable to C<< $notify->language . '.' .
353             $notify->encoding >> before executing C and C (but not for
354             sending data to Net::SMTP). Undefined by default, meaning that no
355             Content-Language header is output and the C<$LANG> environment variable will
356             not be set. See L for more information.
357              
358             =item with_diff
359              
360             svnnotify --with-diff
361             svnnotify -d
362              
363             A boolean value specifying whether or not to include the output of C
364             diff> in the notification email. The diff will be inline at the end of the
365             email unless the C parameter specifies a true value.
366              
367             =item attach_diff
368              
369             svnnotify --attach-diff
370             svnnotify -a
371              
372             A boolean value specifying whether or not to attach the output of C
373             diff> to the notification email. Rather than being inline in the body of the
374             email, this parameter causes SVN::Notify to attach the diff as a separate
375             file, named for the user who triggered the commit and the date and time UTC at
376             which the commit took place. Specifying this parameter to a true value
377             implicitly sets the C parameter to a true value.
378              
379             =item diff_switches
380              
381             svnnotify --diff-switches '--no-diff-added'
382             svnnotify -w '--no-diff-deleted'
383              
384             Switches to pass to C, such as C<--no-diff-deleted> and
385             C<--no-diff-added>. And who knows, maybe someday it will support the same
386             options as C, such as C<--diff-cmd> and C<--extensions>. Only
387             relevant when used with C or C.
388              
389             =item diff_content_type
390              
391             svnnotify --diff-content-type 'text/x-diff'
392              
393             Sets the Content-Type header for attached diffs. The default, if this parameter
394             is not passed, is 'text/plain'. This parameter has no effect if '--attach-diff'
395             is not specified.
396              
397             =item reply_to
398              
399             svnnotify --reply-to devlist@example.com
400             svnnotify -R developers@example.net
401              
402             The email address to use in the "Reply-To" header of the notification email.
403             No "Reply-To" header will be added to the email if no value is specified for
404             the C parameter.
405              
406             =item add_headers
407              
408             svnnotify --add-header X-Approve=letMeIn
409              
410             Add a header to the notification email message. The header name and its value
411             must be separated by an equals sign. Specify the option multiple times in
412             order to add multiple headers. Headers with the same names are allowed. Not to
413             be confused with the C<--header> option, which adds introductory text to the
414             beginning of the email body.
415              
416             =item subject_prefix
417              
418             svnnotify --subject-prefix [Devlist]
419             svnnotify -P [%d (Our-Developers)]
420              
421             An optional string to prepend to the beginning of the subject line of the
422             notification email. If it contains '%d', it will be used to place the revision
423             number; otherwise it will simply be prepended to the subject, which will
424             contain the revision number in brackets.
425              
426             =item subject_cx
427              
428             svnnotify --subject-cx
429             svnnotify -C
430              
431             A boolean value indicating whether or not to include a the context of the
432             commit in the subject line of the email. In a commit that affects multiple
433             files, the context will be the name of the shortest directory affected by the
434             commit. This should indicate up to how high up the Subversion repository tree
435             the commit had an effect. If the commit affects a single file, then the
436             context will simply be the name of that file.
437              
438             =item strip_cx_regex
439              
440             svnnotify --strip-cx-regex '^trunk/'
441             svnnotify --strip-cx-regex '^trunk/' --strip-cx-regex '^branches/'
442             svnnotify -X '^trunk'
443             svnnotify -X '^trunk' -X '^branches'
444              
445             One or more regular expressions to be used to strip out parts of the subject
446             context. This can be useful for very deep Subversion trees, where the commits
447             you're sending will always be sent from a particular subtree, so you'd like to
448             remove part of the tree. Used only if C is set to a true value.
449             Pass an array reference if calling C directly.
450              
451             =item no_first_line
452              
453             svnnotify --no-first-line
454             svnnotify -O
455              
456             Omits the first line of the log message from the subject. This is most useful
457             when used in combination with the C parameter, so that just the
458             commit context is displayed in the subject and no part of the log message.
459              
460             =item header
461              
462             svnnotify --header 'SVN::Notify is brought to you by Kineticode.
463              
464             Adds a specified text to each message as a header at the beginning of the body
465             of the message. Not to be confused with the C<--add-header> option, which adds
466             a header to the headers section of the email.
467              
468             =item footer
469              
470             svnnotify --footer 'Copyright (R) by Kineticode, Inc.'
471              
472             Adds a specified text to each message as a footer at the end of the body of
473             the message.
474              
475             =item max_sub_length
476              
477             svnnotify --max-sub-length 72
478             svnnotify -i 76
479              
480             The maximum length of the notification email subject line. SVN::Notify
481             includes the first line of the commit log message, or the first sentence of
482             the message (defined as any text up to the string ". "), whichever is
483             shorter. This could potentially be quite long. To prevent the subject from
484             being over a certain number of characters, specify a maximum length here, and
485             SVN::Notify will truncate the subject to the last word under that length.
486              
487             =item max_diff_length
488              
489             svnnotify --max-diff-length 1024
490              
491             The maximum length of the diff (attached or in the body). The diff output is
492             truncated at the last line under the maximum character count specified and
493             then outputs an additional line indicating that the maximum diff size was
494             reached and output truncated. This is helpful when a large diff output could
495             cause a message to bounce due to message size.
496              
497             =item handler
498              
499             svnnotify --handler HTML
500             svnnotify -H HTML
501              
502             Specify the subclass of SVN::Notify to be constructed and returned, and
503             therefore to handle the notification. Of course you can just use a subclass
504             directly, but this parameter is designed to make it easy to just use
505             C<< SVN::Notify->new >> without worrying about loading subclasses, such as in
506             F. Be sure to read the documentation for your subclass of choice,
507             as there may be additional parameters and existing parameters may behave
508             differently.
509              
510             =item filters
511              
512             svnnotify --filter Trac -F My::Filter
513              
514             SVN::Notify->new( %params, filters => ['Markdown', 'My::Filter'] );
515              
516             Specify a more module to be loaded in the expectation that it defines output
517             filters. For example, L
518             loads a filter that converts log messages from Trac's markup format to HTML.
519             L, available on
520             CPAN, does the same for Markdown format. Check CPAN for other SVN::Notify
521             filter modules.
522              
523             This command-line option can be specified more than once to load multiple
524             filters. The C parameter to C should be an array reference of
525             modules names. If a value contains "::", it is assumed to be a complete module
526             name. Otherwise, it is assumed to be in the SVN::Notify::Filter name space.
527             See L for details on writing your own
528             output filters (it's really easy, I promise!).
529              
530             =item author_url
531              
532             svnnotify --author-url 'http://svn.example.com/changelog/~author=%s/repos'
533             svnnotify --A 'mailto:%s@example.com'
534              
535             If a URL is specified for this parameter, then it will be used to create a
536             link for the current author. The URL can have the "%s" format where the
537             author's username should be put into the URL.
538              
539             =item revision_url
540              
541             svnnotify --revision-url 'http://svn.example.com/changelog/?cs=%s'
542             svnnotify -U 'http://svn.example.com/changelog/?cs=%s'
543              
544             If a URL is specified for this parameter, then it will be used to create a
545             link to the Subversion browser URL corresponding to the current revision
546             number. It will also be used to create links to any other revision numbers
547             mentioned in the commit message. The URL must have the "%s" format where the
548             Subversion revision number should be put into the URL.
549              
550             =item svnweb_url
551              
552             svnnotify --svnweb-url 'http://svn.example.com/index.cgi/revision/?rev=%s'
553             svnnotify -S 'http://svn.example.net/index.cgi/revision/?rev=%s'
554              
555             Deprecated. Use C instead.
556              
557             =item viewcvs_url
558              
559             svnnotify --viewcvs-url 'http://svn.example.com/viewcvs/?rev=%s&view=rev'
560              
561             Deprecated. Use C instead.
562              
563             =item ticket_map
564              
565             svnnotify --ticket-map '\[?#\s*(\d+)\s*\]?=http://example.com/ticket?id=%s' \
566             --ticket-map 'rt=http://rt.cpan.org/NoAuth/Bugs.html?id=%s' \
567             --ticket-map '\b([A-Z0-9]+-\d+)\b=http://jira/browse/%s'
568              
569             Specifies a mapping between a regular expression and a URL. The regular
570             expression should return a single match to be interpolated into the URL, which
571             should be a C format using "%s" to place the match (usually the
572             ticket identifier) from the regex. The command-line option may be specified
573             any number of times for different ticketing systems. To the API, it must be
574             passed as a hash reference.
575              
576             The first example matches "[#1234]" or "#1234" or "[# 1234]". This regex
577             should be as specific as possible, preferably wrapped in "\b" to match word
578             boundaries. If you're using L, be sure to
579             read its documentation for a different regular expression requirement!
580              
581             Optionally, the key value can be a placeholder for a regular expression used
582             internally by SVN::Notify to match strings typically used for well-known
583             ticketing systems. Those keys are:
584              
585             =over
586              
587             =item rt
588              
589             Matches Request Tracker (RT) ticket references of the form "Ticket # 12",
590             "ticket 6", "RT # 52", "rt 52", "RT-Ticket # 213" or even "Ticket#1066".
591              
592             =item bugzilla
593              
594             Matches Bugzilla bug references of the form "Bug # 12" or "bug 6" or even
595             "Bug#1066".
596              
597             =item jira
598              
599             Matches JIRA references of the form "JRA-1234".
600              
601             =item gnats
602              
603             Matches GnatsWeb references of the form "PR 1234".
604              
605             =back
606              
607             =item rt_url
608              
609             svnnotify --rt-url 'http://rt.cpan.org/NoAuth/Bugs.html?id=%s'
610             svnnotify -T 'http://rt.perl.org/NoAuth/Bugs.html?id=%s'
611              
612             A shortcut for C<--ticket-map 'rt=$url'> provided for backwards compatibility.
613              
614             =item bugzilla_url
615              
616             svnnotify --bugzilla-url 'http://bugzilla.mozilla.org/show_bug.cgi?id=%s'
617             svnnotify -B 'http://bugs.bricolage.cc/show_bug.cgi?id=%s'
618              
619             A shortcut for C<--ticket-map 'bugzilla=$url'> provided for backwards
620             compatibility.
621              
622             =item jira_url
623              
624             svnnotify --jira-url 'http://jira.atlassian.com/secure/ViewIssue.jspa?key=%s'
625             svnnotify -J 'http://nagoya.apache.org/jira/secure/ViewIssue.jspa?key=%s'
626              
627             A shortcut for C<--ticket-map 'jira=$url'> provided for backwards
628             compatibility.
629              
630             =item gnats_url
631              
632             svnnotify --gnats-url 'http://gnatsweb.example.com/cgi-bin/gnatsweb.pl?cmd=view&pr=%s'
633             svnnotify -G 'http://gnatsweb.example.com/cgi-bin/gnatsweb.pl?cmd=view&pr=%s'
634              
635             A shortcut for C<--ticket-map 'gnats=$url'> provided for backwards
636             compatibility.
637              
638             =item ticket_url
639              
640             svnnotify --ticket-url 'http://ticket.example.com/showticket.html?id=%s'
641              
642             Deprecated. Use C, instead.
643              
644             =item ticket_regex
645              
646             svnnotify --ticket-regex '\[?#\s*(\d+)\s*\]?'
647              
648             Deprecated. Use C, instead.
649              
650             =item verbose
651              
652             svnnotify --verbose -V
653              
654             A value between 0 and 3 specifying how verbose SVN::Notify should be. The
655             default is 0, meaning that SVN::Notify will be silent. A value of 1 causes
656             SVN::Notify to output some information about what it's doing, while 2 and 3
657             each cause greater verbosity. To set the verbosity on the command line, simply
658             pass the C<--verbose> or C<-V> option once for each level of verbosity, up to
659             three times. Output from SVN::Notify is sent to C.
660              
661             =item boundary
662              
663             The boundary to use between email body text and attachments. This is normally
664             generated by SVN::Notify.
665              
666             =item subject
667              
668             The subject of the email to be sent. This attribute is normally generated by
669             C.
670              
671             =back
672              
673             =cut
674              
675             # XXX Sneakily used by SVN::Notify::HTML. Change to use class methods?
676             our %_ticket_regexen = (
677             rt => '\b((?:rt|(?:rt-)?ticket:?)\s*#?\s*(\d+))\b',
678             bugzilla => '\b(bug\s*#?\s*(\d+))\b',
679             jira => '\b([A-Z0-9]+-\d+)\b',
680             gnats => '\b(PR\s*(\d+))\b',
681             );
682              
683             sub new {
684 3406     3406 1 5628188 my ($class, %params) = @_;
685              
686             # Delegate to a subclass if requested.
687 3406 100       25567 if (my $handler = delete $params{handler}) {
688 346         2353 my $subclass = __PACKAGE__ . "::$handler";
689 346 50       1422 unless ($subclass eq $class) {
690 346 50       28152 eval "require $subclass" or die $@;
691 346         4973 return $subclass->new(%params);
692             }
693             }
694              
695             # Load any filters.
696 3060   100     38718 $params{filters} ||= {};
697 3060 100       19182 if (ref $params{filters} eq 'ARRAY') {
698 716         1901 my $filts = {};
699 716         1469 for my $pkg ( @{ $params{filters} } ) {
  716         6228  
700 778 100       8982 $pkg = "SVN::Notify::Filter::$pkg" if $pkg !~ /::/;
701 778 100       4445 if ($filters{$pkg}) {
702 308         1375 while (my ($k, $v) = each %{ $filters{$pkg} }) {
  876         6832  
703 568   50     6216 $filts->{$k} ||= [];
704 568         837 push @{ $filts->{$k} }, $v;
  568         1670  
705             }
706             } else {
707 470 50       44519 eval "require $pkg" or die $@;
708 470         3134 $filters{$pkg} = {};
709 293     293   1803 no strict 'refs';
  293         484  
  293         897718  
710 470         1264 while ( my ($k, $v) = each %{ "$pkg\::" } ) {
  1355         14807  
711 885         5701 my $code = ref \$v eq 'GLOB' ? *{$v}{CODE}
712             : ref $v eq 'CODE' ? $v
713 885 0       3590 : *{ "$pkg\::$k" }{CODE} or next;
  0 50       0  
    100          
714 769         2701 $filters{$pkg}->{$k} = $code;
715 769   100     7168 $filts->{$k} ||= [];
716 769         1314 push @{ $filts->{$k} }, $code;
  769         4172  
717             }
718             }
719             }
720 716         2090 $params{filters} = $filts;
721             }
722              
723             # Make sure that the tos are an arrayref.
724 3060 100 66     16250 $params{to} = [ $params{to} || () ] unless ref $params{to};
725              
726             # Check for required parameters.
727             $class->_dbpnt( "Checking required parameters to new()")
728 3060 100       12517 if $params{verbose};
729             _usage( qq{Missing required "repos_path" parameter} )
730 3060 100       14709 unless $params{repos_path};
731             _usage( qq{Missing required "revision" parameter} )
732 3059 100       10723 unless $params{revision};
733              
734             # Set up default values.
735 3058   0     11306 $params{svnlook} ||= $ENV{SVNLOOK} || $class->find_exe('svnlook');
      33        
736 3058   100     31442 $params{with_diff} ||= $params{attach_diff};
737 3058   100     34890 $params{verbose} ||= 0;
738 3058   50     36512 $params{encoding} ||= $params{charset} || 'UTF-8';
      66        
739 3058   66     20083 $params{svn_encoding} ||= $params{encoding};
740 3058   66     24966 $params{diff_encoding} ||= $params{svn_encoding};
741 3058   50     34079 $params{diff_content_type} ||= $params{diff_content_type} || 'text/plain';
      66        
742             $params{sendmail} ||= $ENV{SENDMAIL} || $class->find_exe('sendmail')
743 3058 100 0     17072 unless $params{smtp};
      33        
744              
745             _usage( qq{Cannot find sendmail and no "smtp" parameter specified} )
746 3058 50 66     11867 unless $params{sendmail} || $params{smtp};
747              
748             # Set up the environment locale.
749 3058 100 66     14725 if ( $params{language} && !$ENV{LANG} ) {
750 210         3248 ( my $lang_country = $params{language} ) =~ s/-/_/g;
751 210         1360 for my $p (qw(encoding svn_encoding)) {
752 420         1098 my $encoding = $params{$p};
753 420 50       1496 $encoding =~ s/-//g if uc($encoding) ne 'UTF-8';
754 420         3491 (my $label = $p ) =~ s/(_?)encoding/$1/;
755 420         2239 $params{"${label}env_lang"} = "$lang_country.$encoding";
756             }
757             }
758              
759             # Set up the revision URL.
760             $params{revision_url} ||= delete $params{svnweb_url}
761 3058   100     48848 || delete $params{viewcvs_url};
      100        
762 3058 50 66     15231 if ($params{revision_url} && $params{revision_url} !~ /%s/) {
763 0         0 warn "--revision-url must have '%s' format\n";
764 0         0 $params{revision_url} .= '/revision/?rev=%s&view=rev'
765             }
766              
767             # Set up the issue tracking links.
768 3058         10247 my $track = $params{ticket_map};
769 3058 100       10442 if ($params{ticket_regex}) {
770 77         616 $track->{ delete $params{ticket_regex} } = delete $params{ticket_url};
771             }
772              
773 3058         13818 for my $system (qw(rt bugzilla jira gnats)) {
774 12232         24270 my $param = $system . '_url';
775 12232 100       26548 if ($params{ $param }) {
776 283         915 $track->{ $system } = delete $params{ $param };
777             warn "--$system-url must have '%s' format\n"
778 283 50       1468 unless $track->{ $system } =~ /%s/;
779             }
780             }
781 3058 100       8573 $params{ticket_map} = $track if $track;
782              
783             # Make it so!
784 3058 100       8458 $class->_dbpnt( "Instantiating $class object") if $params{verbose};
785 3058         168361 return bless \%params, $class;
786             }
787              
788             ##############################################################################
789              
790             =head2 Class Methods
791              
792             =head3 content_type
793              
794             my $content_type = SVN::Notify->content_type;
795              
796             Returns the content type of the notification message, "text/plain". Used to
797             set the Content-Type header for the message.
798              
799             =cut
800              
801 1617     1617 1 15490 sub content_type { 'text/plain' }
802              
803             ##############################################################################
804              
805             =head3 register_attributes
806              
807             SVN::Notify::Subclass->register_attributes(
808             foo_attr => 'foo-attr=s',
809             bar => 'bar',
810             bat => undef,
811             );
812              
813             This class method is used by subclasses to register new attributes. Pass in a
814             list of key/value pairs, where the keys are the attribute names and the values
815             are option specifications in the format required by Getopt::Long. SVN::Notify
816             will create accessors for each attribute, and if the corresponding value is
817             defined, it will be used by the C class method to get a
818             command-line option value.
819              
820             See for an example usage of
821             C.
822              
823             =cut
824              
825             my %OPTS;
826              
827             sub register_attributes {
828 269     269 1 717 my $class = shift;
829 269         556 my @attrs;
830 269         1352 while (@_) {
831 665         1269 push @attrs, shift;
832 665 50       1510 if (my $opt = shift) {
833 665         2490 $OPTS{$attrs[-1]} = $opt;
834             }
835             }
836 269         2571 $class->_accessors(@attrs);
837             }
838              
839             ##############################################################################
840              
841             =head3 get_options
842              
843             my $options = SVN::Notify->get_options;
844              
845             Parses the command-line options in C<@ARGV> to a hash reference suitable for
846             passing as the parameters to C. See L<"new"> for a complete list of the
847             supported parameters and their corresponding command-line options.
848              
849             This method use Getopt::Long to parse C<@ARGV>. It then looks for any
850             C and C options and, if it finds any, loads the appropriate
851             classes and parses any options they requires from C<@ARGV>. Subclasses and
852             filter classes should use C to register any attributes
853             and options they require.
854              
855             After that, on Perl 5.8 and later, it decodes all of the string option from
856             the encoding specified by the C option or UTF-8. This allows options
857             to be passed to SVN::Notify in that encoding and end up being displayed
858             properly in the resulting notification message.
859              
860             =cut
861              
862             sub get_options {
863 4     4 1 28915 my $class = shift;
864 4         9 my $opts = {};
865 4         608 require Getopt::Long;
866              
867             # Enable bundling and, at the same time, case-sensitive matching of
868             # single character options. Also enable pass-through so that subclasses
869             # can grab more options.
870 4         7473 Getopt::Long::Configure (qw(bundling pass_through));
871              
872             # Get options.
873             Getopt::Long::GetOptions(
874             'repos-path|p=s' => \$opts->{repos_path},
875             'revision|r=s' => \$opts->{revision},
876             'to|t=s@' => \$opts->{to},
877             'to-regex-map|x=s%' => \$opts->{to_regex_map},
878             'to-email-map=s%' => \$opts->{to_email_map},
879             'from|f=s' => \$opts->{from},
880             'user-domain|D=s' => \$opts->{user_domain},
881             'svnlook|l=s' => \$opts->{svnlook},
882             'sendmail|s=s' => \$opts->{sendmail},
883             'set-sender|E' => \$opts->{set_sender},
884             'smtp=s' => \$opts->{smtp},
885             'smtp-port=i' => \$opts->{smtp_port},
886             'smtp-tls!' => \$opts->{smtp_tls},
887             'encoding|charset|c=s'=> \$opts->{encoding},
888             'diff-encoding=s' => \$opts->{diff_encoding},
889             'svn-encoding=s' => \$opts->{svn_encoding},
890             'language|g=s' => \$opts->{language},
891             'with-diff|d' => \$opts->{with_diff},
892             'attach-diff|a' => \$opts->{attach_diff},
893             'diff-switches|w=s' => \$opts->{diff_switches},
894             'diff-content-type=s' => \$opts->{diff_content_type},
895             'reply-to|R=s' => \$opts->{reply_to},
896             'subject-prefix|P=s' => \$opts->{subject_prefix},
897             'subject-cx|C' => \$opts->{subject_cx},
898             'strip-cx-regex|X=s@' => \$opts->{strip_cx_regex},
899             'no-first-line|O' => \$opts->{no_first_line},
900             'max-sub-length|i=i' => \$opts->{max_sub_length},
901             'max-diff-length|e=i' => \$opts->{max_diff_length},
902             'handler|H=s' => \$opts->{handler},
903             'filter|F=s@' => \$opts->{filters},
904             'author-url|A=s' => \$opts->{author_url},
905             'ticket-regex=s' => \$opts->{ticket_regex},
906             'ticket-map=s%' => \$opts->{ticket_map},
907             'verbose|V+' => \$opts->{verbose},
908             'help|h' => \$opts->{help},
909             'man|m' => \$opts->{man},
910             'version|v' => \$opts->{version},
911             'header=s' => \$opts->{header},
912             'footer=s' => \$opts->{footer},
913             'smtp-user=s' => \$opts->{smtp_user},
914             'smtp-pass=s' => \$opts->{smtp_pass},
915             'smtp-authtype=s' => \$opts->{smtp_authtype},
916             'add-header=s%' => sub {
917 6     6   13997 shift; push @{ $opts->{add_headers}{+shift} }, shift
  6         9  
  6         20  
918             },
919             'revision-url|U|svnweb-url|S|viewcvs-url=s' => \$opts->{revision_url},
920             'rt-url|T|bugzilla-url|B|jira-url|J|gnats-url|G|ticket-url=s'
921             => \$opts->{ticket_url},
922 4 50       241 ) or return;
923              
924             # Load a subclass if one has been specified.
925 4 100       706 if (my $hand = $opts->{handler}) {
926 1 50       59 eval "require " . __PACKAGE__ . "::$hand" or die $@;
927 1 50       8 if ($hand eq 'Alternative') {
928             # Load the alternative subclasses.
929             Getopt::Long::GetOptions(
930 0         0 map { delete $OPTS{$_} => \$opts->{$_} } keys %OPTS
  0         0  
931             );
932 0 0       0 for my $alt (@{ $opts->{alternatives} || ['HTML']}) {
  0         0  
933 0 0       0 eval "require " . __PACKAGE__ . "::$alt" or die $@;
934             }
935             }
936             }
937              
938             # Load any filters.
939 4 100       14 if ($opts->{filters}) {
940 1         2 for my $pkg ( @{ $opts->{filters} } ) {
  1         4  
941 1 50       6 $pkg = "SVN::Notify::Filter::$pkg" if $pkg !~ /::/;
942 1 50       72 eval "require $pkg" or die $@;
943             }
944             }
945              
946             # Disallow pass-through so that any invalid options will now fail.
947 4         14 Getopt::Long::Configure (qw(no_pass_through));
948 4         120 my @to_decode;
949 4 100       12 if (%OPTS) {
950             # Get a list of string options we'll need to decode.
951 2         9 @to_decode = map { $OPTS{$_} } grep { /=s$/ } keys %OPTS
  0         0  
  4         12  
952             if PERL58;
953              
954             # Load any other options.
955             Getopt::Long::GetOptions(
956 2         6 map { delete $OPTS{$_} => \$opts->{$_} } keys %OPTS
  4         14  
957             );
958             } else {
959             # Call GetOptions() again so that invalid options will be properly
960             # caught.
961 2         4 Getopt::Long::GetOptions();
962             }
963              
964 4         516 if (PERL58) {
965             # Decode all string options.
966 4   50     22 my $encoding = $opts->{encoding} || 'UTF-8';
967 4         16 for my $opt ( qw(
968             repos_path
969             revision
970             from
971             user_domain
972             svnlook
973             sendmail
974             smtp
975             smtp_tls
976             smtp_port
977             diff_switches
978             reply_to
979             subject_prefix
980             handler
981             author_url
982             ticket_regex
983             header
984             footer
985             smtp_user
986             smtp_pass
987             revision_url
988             ticket_url
989             ), @to_decode ) {
990             $opts->{$opt} = Encode::decode( $encoding, $opts->{$opt} )
991 84 100       1282 if $opts->{$opt};
992             }
993             }
994              
995             # Clear the extra options specifications and return.
996 4         48 %OPTS = ();
997 4         46 return $opts;
998             }
999              
1000             ##############################################################################
1001              
1002             =head3 file_label_map
1003              
1004             my $map = SVN::Notify->file_label_map;
1005              
1006             Returns a hash reference of the labels to be used for the lists of files. A
1007             hash reference of file lists is stored in the C attribute after
1008             C has been called. The hash keys in that list correspond to
1009             Subversion status codes, and these are mapped to their appropriate labels by
1010             the hash reference returned by this method:
1011              
1012             { U => 'Modified Paths',
1013             A => 'Added Paths',
1014             D => 'Removed Paths',
1015             _ => 'Property Changed'
1016             }
1017              
1018             =cut
1019              
1020 2662     2662 1 12865 sub file_label_map { \%map }
1021              
1022             ##############################################################################
1023              
1024             =head3 find_exe
1025              
1026             my $exe = SVN::Notify->find_exe($exe_name);
1027              
1028             This method searches through the system path, as well as the extra directories
1029             F and F (because they're common paths for
1030             C and C for an executable file with the name C<$exe_name>.
1031             The first one it finds is returned with its full path. If none is found,
1032             C returns undef.
1033              
1034             =cut
1035              
1036             sub find_exe {
1037 1     1 1 4 my ($class, $exe) = @_;
1038 1         8 $exe .= '.exe' if WIN32;
1039 1         14 require File::Spec;
1040 1         8 require Config;
1041 1         17 for my $path (
1042             File::Spec->path,
1043             qw(/usr/local/bin /usr/bin /usr/sbin),
1044             'C:\\program files\\subversion\\bin',
1045             $Config::Config{installbin},
1046             $Config::Config{installscript},
1047             ) {
1048 3         62 my $file = File::Spec->catfile($path, $exe);
1049 3 100 66     74 return $file if -f $file && -x _;
1050             }
1051 0         0 return;
1052             }
1053              
1054             ##############################################################################
1055              
1056             =head1 Instance Interface
1057              
1058             =head2 Instance Methods
1059              
1060             =head3 prepare
1061              
1062             $notifier->prepare;
1063              
1064             Prepares the SVN::Notify object, collecting all the data it needs in
1065             preparation for sending the notification email. Really it's just a shortcut
1066             for:
1067              
1068             $notifier->prepare_recipients;
1069             $notifier->prepare_contents;
1070             $notifier->prepare_files;
1071             $notifier->prepare_subject;
1072              
1073             Only it returns after the call to C if there are no
1074             recipients (that is, as when recipients are specified solely by the
1075             C or C parameter and none of the regular
1076             expressions match any of the affected directories).
1077              
1078             =cut
1079              
1080             sub prepare {
1081 2714     2714 1 11050 my $self = shift;
1082 2714         17669 $self->run_filters('pre_prepare');
1083             _usage(
1084             qq{Missing required "to", "to_regex_map", or "to_email_map" parameter}
1085 2714 50 66     4645 ) unless @{$self->{to}} || $self->{to_regex_map} || $self->{to_email_map};
  2714   33     11446  
1086 2713         21706 $self->prepare_recipients;
1087 2707 50       6691 return $self unless @{ $self->{to} };
  2707         13634  
1088 2707         33496 $self->prepare_contents;
1089 2624         37764 $self->prepare_files;
1090 2541         45694 $self->prepare_subject;
1091 2541         19484 $self->run_filters('post_prepare');
1092 2541         80673 return $self;
1093             }
1094              
1095             ##############################################################################
1096              
1097             =head3 prepare_recipients
1098              
1099             $notifier->prepare_recipients;
1100              
1101             Collects and prepares a list of the notification recipients. The recipients
1102             are a combination of the value passed to the C parameter as well as any
1103             email addresses specified as keys in the hash reference passed C
1104             parameter or values passed to the C parameter, where the
1105             corresponding regular expressions stored in the hash matches one or more of
1106             the names of the directories affected by the commit.
1107              
1108             If the F parameter to C has a true value,
1109             C also determines the directory name to use for the
1110             context.
1111              
1112             =cut
1113              
1114             sub prepare_recipients {
1115 2799     2799 1 6875 my $self = shift;
1116 2799 100       10299 $self->_dbpnt( "Preparing recipients list") if $self->{verbose};
1117 2799 100 100     42739 unless (
      100        
1118             $self->{to_regex_map}
1119             || $self->{subject_cx}
1120             || $self->{to_email_map}
1121             ) {
1122 2493         11370 $self->{to} = $self->run_filters( recipients => $self->{to} );
1123 2493         30524 return $self;
1124             }
1125              
1126             # Prevent duplication.
1127 306         1248 my $tos = $self->{to} = [ @{ $self->{to} } ];
  306         2211  
1128              
1129             my $regexen = $self->{to_regex_map} && $self->{to_email_map}
1130 0         0 ? [ %{ $self->{to_regex_map} }, reverse %{ $self->{to_email_map } } ]
  0         0  
1131 75         675 : $self->{to_regex_map} ? [ %{ $self->{to_regex_map} } ]
1132 306 100 66     6768 : $self->{to_email_map} ? [ reverse %{ $self->{to_email_map } } ]
  71 100       284  
    50          
1133             : undef;
1134              
1135 306 100       1653 if ($regexen) {
1136             $self->_dbpnt( "Compiling regex_map regular expressions")
1137 146 50       813 if $self->{verbose} > 1;
1138 146         1109 for (my $i = 1; $i < @$regexen; $i += 2) {
1139 438 50       1626 $self->_dbpnt( qq{Compiling "$_"}) if $self->{verbose} > 2;
1140             # Remove initial slash and compile.
1141 438         2115 $regexen->[$i] =~ s|^\^[/\\]|^|;
1142 438         5986 $regexen->[$i] = qr/$regexen->[$i]/;
1143             }
1144             } else {
1145 160         600 $regexen = [];
1146             }
1147              
1148 306 50       2269 local $ENV{LANG} = "$self->{svn_env_lang}" if $self->{svn_env_lang};
1149             my $fh = $self->_pipe(
1150             $self->{svn_encoding},
1151             '-|', $self->{svnlook},
1152             'changed',
1153             $self->{repos_path},
1154             '-r', $self->{revision},
1155 306         2230 );
1156              
1157             # Read in a list of the files changed.
1158 300         6300 my ($cx, %seen);
1159 300         175364322 while (<$fh>) {
1160 4993         36426 s/^.\s*//;
1161 4993         37095 s/[\n\r\/\\]+$//;
1162 4993         15192 for (my $i = 0; $i < @$regexen; $i += 2) {
1163 8640         8434 my ($email, $rx) = @{$regexen}[$i, $i + 1];
  8640         12248  
1164             # If the file matches the regex, save the email.
1165 8640 100       43542 if (/$rx/) {
1166 2448 50       4822 $self->_dbpnt( qq{"$_" matched $rx}) if $self->{verbose} > 2;
1167 2448 100       8158 push @$tos, $email unless $seen{$email}++;
1168             }
1169             }
1170             # Grab the context if it's needed for the subject.
1171 4993 100       16183 if ($self->{subject_cx}) {
1172             # XXX Do we need to set utf8 here?
1173 2113         89770 my $l = length;
1174 2113   66     11582 $cx ||= $_;
1175 2113   100     89578 $cx =~ s{[/\\]?[^/\\]+$}{} until !$cx || m{^\Q$cx\E(?:$|/|\\)};
1176             }
1177             }
1178             $self->_dbpnt( qq{Context is "$cx"})
1179 300 50 66     4010 if $self->{subject_cx} && $self->{verbose} > 1;
1180 300 50       16918 close $fh or warn "Child process exited: $?\n";
1181 300         3917 $self->{cx} = $cx;
1182 300         6280 $tos = $self->run_filters( recipients => $tos );
1183             $self->_dbpnt( 'Recipients: "', join(', ', @$tos), '"')
1184 300 50       2071 if $self->{verbose} > 1;
1185 300         10425 return $self;
1186             }
1187              
1188             ##############################################################################
1189              
1190             =head3 prepare_contents
1191              
1192             $notifier->prepare_contents;
1193              
1194             Prepares the contents of the commit message, including the name of the user
1195             who triggered the commit (and therefore the contents of the "From" header to
1196             be used in the email) and the log message.
1197              
1198             =cut
1199              
1200             sub prepare_contents {
1201 2793     2793 1 8720 my $self = shift;
1202 2793 100       10277 $self->_dbpnt( "Preparing contents") if $self->{verbose};
1203 2793 100       11003 local $ENV{LANG} = "$self->{svn_env_lang}" if $self->{svn_env_lang};
1204             my $lines = $self->_read_pipe($self->{svnlook}, 'info', $self->{repos_path},
1205 2793         20382 '-r', $self->{revision});
1206 2709         36430 $self->{user} = shift @$lines;
1207 2709         32365 $self->{date} = shift @$lines;
1208 2709         23124 $self->{message_size} = shift @$lines;
1209 2709         22576 $self->{message} = $lines;
1210              
1211             # Set up the from address.
1212 2709 50       14767 unless ($self->{from}) {
1213             $self->{from} = $self->{user}
1214 2709 100       40681 . ( $self->{user_domain} ? "\@$self->{user_domain}" : '' );
1215             }
1216 2709         50312 $self->{from} = $self->run_filters( from => $self->{from} );
1217              
1218 2709 100       15585 if ($self->{verbose} > 1) {
1219 2         86 $self->_dbpnt( "From: $self->{from}");
1220 2         56 $self->_dbpnt( "Message: @$lines");
1221             }
1222 2709         28726 return $self;
1223             }
1224              
1225             ##############################################################################
1226              
1227             =head3 prepare_files
1228              
1229             $notifier->prepare_files;
1230              
1231             Prepares the lists of files affected by the commit, sorting them into their
1232             categories: modified files, added files, and deleted files. It also compiles a
1233             list of files wherein a property was set, which might have some overlap with
1234             the list of modified files (if a single commit both modified a file and set a
1235             property on it).
1236              
1237             If the C parameter was specified and a single file was affected by
1238             the commit, then C will also specify that file name as the
1239             context to be used in the subject line of the commit email.
1240              
1241             =cut
1242              
1243             sub prepare_files {
1244 2709     2709 1 11380 my $self = shift;
1245 2709 100       12432 $self->_dbpnt( "Preparing file lists") if $self->{verbose};
1246 2709         8506 my %files;
1247 2709 100       15381 local $ENV{LANG} = "$self->{svn_env_lang}" if $self->{svn_env_lang};
1248             my $fh = $self->_pipe(
1249             $self->{svn_encoding},
1250             '-|', $self->{svnlook},
1251             'changed',
1252             $self->{repos_path},
1253             '-r', $self->{revision},
1254 2709         29877 );
1255              
1256             # Read in a list of changed files.
1257 2625         1700754520 my $cx = $_ = <$fh>;
1258 2625         116739 do {
1259 46761         395133 s/[\n\r]+$//;
1260 46761 50       258185 if (s/^(.)(.)\s+//) {
1261 46761 50       116050 $self->_dbpnt( "$1,$2 => $_") if $self->{verbose} > 2;
1262 46761         65239 push @{$files{$1}}, $_;
  46761         291188  
1263 46761 100 100     495309 push @{$files{_}}, $_ if $2 ne ' ' && $1 ne '_';
  2404         25566  
1264             }
1265             } while (<$fh>);
1266              
1267 2625 100 100     27011 if ($self->{subject_cx} && $. == 1) {
1268             # There's only one file; it's the context.
1269 51         1479 $cx =~ s/[\n\r]+$//;
1270 51         969 ($self->{cx} = $cx) =~ s/^..\s+//;
1271             $self->_dbpnt( qq{File context is "$self->{cx}"})
1272 51 50       1020 if $self->{verbose} > 1;
1273             }
1274             # Wait till we get here to close the file handle, otherwise $. gets reset
1275             # to 0!
1276 2625 50       153922 close $fh or warn "Child process exited: $?\n";
1277 2625         36974 $self->{files} = \%files;
1278 2625         58623 return $self;
1279             }
1280              
1281             ##############################################################################
1282              
1283             =head3 prepare_subject
1284              
1285             $notifier->prepare_subject;
1286              
1287             Prepares the subject line for the notification email. This method B be
1288             called after C and C, since each of
1289             those methods potentially sets up the context for use in the the subject
1290             line. The subject may have a prefix defined by the C parameter
1291             to C, it has the revision number, it might have the context if the
1292             C specified a true value, and it will have the first sentence or
1293             line of the commit, whichever is shorter. The subject may then be truncated to
1294             the maximum length specified by the C parameter.
1295              
1296             =cut
1297              
1298             sub prepare_subject {
1299 2625     2625 1 11476 my $self = shift;
1300 2625 100       24748 $self->_dbpnt( "Preparing subject") if $self->{verbose};
1301              
1302 2625         21608 $self->{subject} = '';
1303              
1304             # Start with the optional message and revision number..
1305 2625 100       15920 if ( defined $self->{subject_prefix} ) {
1306 127 100       2019 if ( index($self->{subject_prefix}, '%d') > 0 ) {
1307             $self->{subject} .=
1308 59         1947 sprintf $self->{subject_prefix}, $self->{revision};
1309             } else {
1310             $self->{subject} .=
1311 68         1378 $self->{subject_prefix} . "[$self->{revision}] ";
1312             }
1313             } else {
1314 2498         20306 $self->{subject} .= "[$self->{revision}] ";
1315             }
1316              
1317             # Add the context if there is one.
1318 2625 100       14129 if ($self->{cx}) {
1319 148 100       2585 if (my $rx = $self->{strip_cx_regex}) {
1320 42         2232 $self->{cx} =~ s/$_// for @$rx;
1321             }
1322 148 100       2034 my $space = $self->{no_first_line} ? '' : ': ';
1323 148 50       2844 $self->{subject} .= $self->{cx} . $space if $self->{cx};
1324             }
1325              
1326             # Add the first sentence/line from the log message.
1327 2625 100       12890 unless ($self->{no_first_line}) {
1328             # Truncate to first period after a minimum of 10 characters.
1329 2583         21596 my $min = length $self->{message}[0];
1330 2583 50       13758 $min = 10 if $min > 10;
1331 2583         36708 my $i = index substr($self->{message}[0], $min), '. ';
1332             $self->{subject} .= $i > 0
1333             ? substr($self->{message}[0], 0, $i + 11)
1334 2583 100       43779 : $self->{message}[0];
1335             }
1336              
1337             # Truncate to the last word under 72 characters.
1338             $self->{subject} =~ s/^(.{0,$self->{max_sub_length}})\s+.*$/$1/m
1339             if $self->{max_sub_length}
1340 2625 100 66     20466 && length $self->{subject} > $self->{max_sub_length};
1341              
1342             # Now filter it.
1343 2625         42516 $self->{subject} = $self->run_filters( subject => $self->{subject} );
1344 2625 100       12738 $self->_dbpnt( qq{Subject is "$self->{subject}"}) if $self->{verbose};
1345              
1346 2625         8974 return $self;
1347             }
1348              
1349             ##############################################################################
1350              
1351             =head3 execute
1352              
1353             $notifier->execute;
1354              
1355             Sends the notification message. This involves opening a file handle to
1356             F or a tied file handle connected to an SMTP server and passing it
1357             to C. This is the main method used to send notifications or execute
1358             any other actions in response to Subversion activity.
1359              
1360             =cut
1361              
1362             sub execute {
1363 2625     2625 1 16069 my $self = shift;
1364 2625 100       18706 $self->_dbpnt( "Sending message") if $self->{verbose};
1365 2625         11916 $self->run_filters('pre_execute');
1366 2625 50       4962 return $self unless @{ $self->{to} };
  2625         18282  
1367              
1368 2625 100       12594 my $out = $self->{smtp} ? SVN::Notify::SMTP->get_handle($self) : do {
1369 2621 100       22411 local $ENV{LANG} = $self->{env_lang} if $self->{env_lang};
1370             $self->_pipe(
1371             $self->{encoding},
1372             '|-', $self->{sendmail},
1373             '-oi', '-t',
1374 2621 50       29298 ($self->{set_sender} ? ('-f', $self->{from}) : ())
1375             );
1376             };
1377              
1378             # Output the message.
1379 2543         93257 $self->output($out);
1380              
1381 2514 50       1220864408 close $out or warn "Child process exited: $?\n";
1382 2514 100       38086 $self->_dbpnt( 'Message sent' ) if $self->{verbose};
1383 2514         20051 $self->run_filters('post_execute');
1384 2514         98554 return $self;
1385             }
1386              
1387             ##############################################################################
1388              
1389             =head3 output
1390              
1391             $notifier->output($file_handle);
1392             $notifier->output($file_handle, $no_headers);
1393              
1394             Called internally by C to output a complete email message. The file
1395             a file handle, so that C and its related methods can print directly
1396             to the email message. The optional second argument, if true, will suppress the
1397             output of the email headers.
1398              
1399             Really C is a simple wrapper around a number of other method calls.
1400             It is thus essentially a shortcut for:
1401              
1402             $notifier->output_headers($out) unless $no_headers;
1403             $notifier->output_content_type($out);
1404             $notifier->start_body($out);
1405             $notifier->output_metadata($out);
1406             $notifier->output_log_message($out);
1407             $notifier->output_file_lists($out);
1408             if ($notifier->with_diff) {
1409             my $diff_handle = $self->diff_handle;
1410             if ($notifier->attach_diff) {
1411             $notifier->end_body($out);
1412             $notifier->output_attached_diff($out, $diff_handle);
1413             } else {
1414             $notifier->output_diff($out, $diff_handle);
1415             $notifier->end_body($out);
1416             }
1417             } else {
1418             $notifier->end_body($out);
1419             }
1420             $notifier->end_message($out);
1421              
1422             =cut
1423              
1424             sub output {
1425 2662     2662 1 21761 my ($self, $out, $no_headers) = @_;
1426 2662 100       38884 $self->_dbpnt( "Outputting notification message") if $self->{verbose} > 1;
1427 2662 100       50679 $self->output_headers($out) unless $no_headers;
1428 2662         38205 $self->output_content_type($out);
1429 2662         27328 $self->start_body($out);
1430 2662         19946 $self->output_metadata($out);
1431 2662         20947 $self->output_log_message($out);
1432 2662         18849 $self->output_file_lists($out);
1433 2662 100       9211 if ($self->{with_diff}) {
1434             # Get a handle on the diff output.
1435 704         8986 my $diff = $self->diff_handle;
1436 675 100       16054 if ($self->{attach_diff}) {
1437 155         7038 $self->end_body($out);
1438 155         6321 $self->output_attached_diff($out, $diff);
1439             } else {
1440 520         14374 $self->output_diff($out, $diff);
1441 520         6396 $self->end_body($out);
1442             }
1443             } else {
1444 1958         11338 $self->end_body($out);
1445             }
1446 2633         20117 $self->end_message($out);
1447              
1448 2633         19725 return $self;
1449             }
1450              
1451             ##############################################################################
1452              
1453             =head3 output_headers
1454              
1455             $notifier->output_headers($file_handle);
1456              
1457             Outputs the headers for the notification message headers. Should be called
1458             only once for a single email message.
1459              
1460             =cut
1461              
1462             sub output_headers {
1463 2543     2543 1 23298 my ($self, $out) = @_;
1464 2543 50       15343 $self->_dbpnt( "Outputting headers") if $self->{verbose} > 2;
1465              
1466             # Q-Encoding (RFC 2047)
1467 293     293   5637 my $subj = PERL58 && $self->{subject} =~ /(?:\P{ASCII}|=)/s
  293         676  
  293         3898  
1468             ? Encode::encode( 'MIME-Q', $self->{subject} )
1469 2543 100       114535 : $self->{subject};
1470              
1471             # Q-Encode the phrase part of recipient headers.
1472 2543         3529963 my $norm;
1473 2543         5628 if (PERL58) {
1474 2543         275479 require Email::Address::XS;
1475             $norm = sub {
1476             return join ', ' => map {
1477 5150     5150   17831 my $addr = Email::Address::XS->parse($_);
  5434         96384  
1478 5434 100       413401 if ($addr->is_valid()) {
1479 2932 50       63145 if (my $phrase = $addr->phrase) {
1480 0         0 $addr->phrase(Encode::encode( 'MIME-Q', $phrase ));
1481             }
1482 2932         37418 $addr->format;
1483             } else {
1484 2502         111725 $_;
1485             }
1486             } @_;
1487 2543         1091865 };
1488             } else {
1489 0     0   0 $norm = sub { join ', ' => @_ };
1490             }
1491 2543         27763 my $from = $norm->($self->{from});
1492 2543         10144 my $to = $norm->(@{ $self->{to} });
  2543         18257  
1493              
1494 2543         220051 my @headers = (
1495             "MIME-Version: 1.0\n",
1496             "X-Mailer: SVN::Notify " . $self->VERSION
1497             . ": http://search.cpan.org/dist/SVN-Notify/\n",
1498             "From: $from\n",
1499             "Errors-To: $from\n",
1500             "To: $to\n",
1501             "Subject: $subj\n"
1502             );
1503              
1504             push @headers, 'Reply-To: ' . $norm->($self->{reply_to}) . "\n"
1505 2543 100       23850 if $self->{reply_to};
1506              
1507 2543 100       14478 if (my $heads = $self->{add_headers}) {
1508 1         22 while (my ($k, $v) = each %{ $heads }) {
  3         28  
1509 2 100       28 push @headers, "$k: $_\n" for ref $v ? @{ $v } : $v;
  1         12  
1510             }
1511             }
1512              
1513 2543         18685 print $out @{ $self->run_filters( headers => \@headers ) };
  2543         65159  
1514 2543         30191 return $self;
1515             }
1516              
1517             ##############################################################################
1518              
1519             =head3 output_content_type
1520              
1521             $notifier->output_content_type($file_handle);
1522              
1523             Outputs the content type and transfer encoding headers. These demarcate the
1524             body of the message. If the C parameter was set to true, then a
1525             boundary string will be generated and the Content-Type set to
1526             "multipart/mixed" and stored as the C attribute.
1527              
1528             After that, this method outputs the content type returned by
1529             C, the character set specified by the C attribute,
1530             and a Content-Transfer-Encoding of "8bit". Subclasses can either rely on this
1531             functionality or override this method to provide their own content type
1532             headers.
1533              
1534             =cut
1535              
1536             sub output_content_type {
1537 2662     2662 1 12565 my ($self, $out) = @_;
1538 2662 50       11499 $self->_dbpnt( "Outputting content type") if $self->{verbose} > 2;
1539             # Output the content type.
1540 2662 100       8145 if ($self->{attach_diff}) {
1541             # We need a boundary string.
1542 159   66     6938 $self->{boundary} ||= join '', ('a'..'z', 'A'..'Z', 0..9)[ map { rand 62 } 0..10];
  946         12836  
1543 159         3028 print $out
1544             qq{Content-Type: multipart/mixed; boundary="$self->{boundary}"\n\n};
1545             }
1546              
1547 2662         39099 my $ctype = $self->content_type;
1548 2662 100       13566 print $out "--$self->{boundary}\n" if $self->{attach_diff};
1549             print $out "Content-Type: $ctype; charset=$self->{encoding}\n",
1550 2662 100       46110 ($self->{language} ? "Content-Language: $self->{language}\n" : ()),
1551             "Content-Transfer-Encoding: 8bit\n\n";
1552 2662         10211 return $self;
1553             }
1554              
1555             ##############################################################################
1556              
1557             =head3 start_body
1558              
1559             $notifier->start_body($file_handle);
1560              
1561             This method starts the body of the notification message, which means that it
1562             outputs the contents of the C
attribute, if there are any. Otherwise
1563             it outputs nothing, but see subclasses for other behaviors.
1564              
1565             =cut
1566              
1567             sub start_body {
1568 1585     1585 1 6189 my ($self, $out) = @_;
1569 1585 100       6766 my $start = [ $self->{header} ? ("$self->{header}\n") : () ];
1570 1585         4333 $start = $self->run_filters( start_body => $start );
1571 1585 100 66     29456 print $out @$start, "\n" if $start && @$start;
1572 1585         5547 return $self;
1573             }
1574              
1575             ##############################################################################
1576              
1577             =head3 output_metadata
1578              
1579             $notifier->output_metadata($file_handle);
1580              
1581             This method outputs the metadata of the commit, including the revision number,
1582             author (user), and date of the revision. If the C or
1583             C attributes have been set, then the appropriate URL(s) for the
1584             revision will also be output.
1585              
1586             =cut
1587              
1588             sub output_metadata {
1589 1610     1610 1 5804 my ($self, $out) = @_;
1590 1610         9237 my @lines = ("Revision: $self->{revision}\n");
1591 1610 100       6771 if (my $url = $self->{revision_url}) {
1592 103         1189 push @lines, sprintf " $url\n", $self->{revision};
1593             }
1594              
1595             # Output the Author any any relevant URL.
1596 1610         10287 push @lines, "Author: $self->{user}\n";
1597 1610 100       7077 if (my $url = $self->{author_url}) {
1598 32         672 push @lines, sprintf " $url\n", $self->{user};
1599             }
1600              
1601 1610         17746 push @lines, "Date: $self->{date}\n";
1602              
1603 1610         6792 print $out @{ $self->run_filters( metadata => \@lines ) };
  1610         5068  
1604 1610         6100 return $self;
1605             }
1606              
1607             ##############################################################################
1608              
1609             =head3 output_log_message
1610              
1611             $notifier->output_log_message($file_handle);
1612              
1613             Outputs the commit log message, as well as the label "Log Message".
1614              
1615             =cut
1616              
1617             sub output_log_message {
1618 1585     1585 1 5279 my ($self, $out) = @_;
1619 1585 100       6810 $self->_dbpnt( "Outputting log message") if $self->{verbose} > 1;
1620             my $msg = join "\n", @{
1621 1585         8920 $self->run_filters( log_message => $self->{message} )
1622 1585         7406 };
1623              
1624 1585         322942 print $out "Log Message:\n-----------\n$msg\n";
1625              
1626             # Make Revision links.
1627 1585 100       7557 if (my $url = $self->{revision_url}) {
1628 103 50       7942 if (my @matches = $msg =~ /\b(?:(?:rev(?:ision)?\s*#?\s*|r)(\d+))\b/ig) {
1629 103         243125 print $out "\nRevision Links:\n--------------\n";
1630 103         2100 printf $out " $url\n", $_ for @matches;
1631             }
1632             }
1633              
1634             # Make ticketing system links.
1635 1585 100       26026 if (my $map = $self->ticket_map) {
1636 26         832 my $has_header = 0;
1637             $self->run_ticket_map( sub {
1638 156     156   546 my ($regex, $url) = @_;
1639 156         11258 while ($msg =~ /$regex/ig) {
1640 234 100       1170 unless ($has_header) {
1641 26         364 print $out "\nTicket Links:\n------------\n";
1642 26         312 $has_header = 1;
1643             }
1644 234   66     8710 printf $out " $url\n", $2 || $1;
1645             }
1646 26         858 } );
1647             }
1648              
1649 1585         4701 return $self;
1650             }
1651              
1652             ##############################################################################
1653              
1654             =head3 output_file_lists
1655              
1656             $notifier->output_file_lists($file_handle);
1657              
1658             Outputs the lists of modified, added, and deleted files, as well as the list
1659             of files for which properties were changed. The labels used for each group are
1660             pulled in from the C class method.
1661              
1662             =cut
1663              
1664             sub output_file_lists {
1665 1607     1607 1 5940 my ($self, $out) = @_;
1666 1607 50       10986 my $files = $self->{files} or return $self;
1667 1607 100       10941 $self->_dbpnt( "Outputting file lists") if $self->{verbose} > 1;
1668 1607         19585 my $map = $self->file_label_map;
1669             # Create the underlines.
1670 1607         26121 my %dash = ( map { $_ => '-' x length($map->{$_}) } keys %$map );
  6428         42802  
1671              
1672 1607         7783 foreach my $type (qw(U A D _)) {
1673             # Skip it if there's nothing to report.
1674 6428 100       20836 next unless $files->{$type};
1675             $self->_dbpnt( " Outputting $map->{$type} file list")
1676 6059 50       13366 if $self->{verbose} > 2;
1677              
1678             # Identify the action and output each file.
1679 6059         14443 print $out "\n", @{ $self->run_filters(
1680             file_lists => [
1681             "$map->{$type}:\n",
1682             "$dash{$type}\n",
1683 6059         19091 map { " $_\n" } @{ $files->{$type} }
  31287         118058  
  6059         47627  
1684             ]
1685             ) };
1686             }
1687 1607         5755 print $out "\n";
1688 1607         5382 return $self;
1689             }
1690              
1691             ##############################################################################
1692              
1693             =head3 end_body
1694              
1695             $notifier->end_body($file_handle);
1696              
1697             Closes out the body of the email by outputting the contents of the C
1698             attribute, if any, and then a couple of newlines. Designed to be called when
1699             the body of the message is complete, and before any call to
1700             C.
1701              
1702             =cut
1703              
1704             sub end_body {
1705 1576     1576 1 7279 my ($self, $out) = @_;
1706 1576 50       6440 $self->_dbpnt( "Ending body") if $self->{verbose} > 2;
1707 1576 100       6986 my $end = [ $self->{footer} ? ("$self->{footer}\n") : () ];
1708 1576         6778 $end = $self->run_filters( end_body => $end );
1709 1576 100 66     20998 print $out @$end, "\n" if $end && @$end;
1710 1576         6788 return $self;
1711             }
1712              
1713             ##############################################################################
1714              
1715             =head3 output_diff
1716              
1717             $notifier->output_diff($out_file_handle, $diff_file_handle);
1718              
1719             Reads diff data from C<$diff_file_handle> and outputs it to to
1720             C<$out_file_handle>.
1721              
1722             =cut
1723              
1724             sub output_diff {
1725 228     228 1 1541 my $self = shift;
1726 228 50       4670 $self->_dbpnt( "Outputting diff") if $self->{verbose} > 1;
1727 228         4016 $self->_dump_diff(@_);
1728             }
1729              
1730             ##############################################################################
1731              
1732             =head3 output_attached_diff
1733              
1734             $notifier->output_attached_diff($out_file_handle, $diff_file_handle);
1735              
1736             Reads diff data from C<$diff_file_handle> and outputs it to to
1737             C<$out_file_handle> as an attachment.
1738              
1739             =cut
1740              
1741             sub output_attached_diff {
1742 155     155 1 1446 my ($self, $out, $diff) = @_;
1743 155 50       6352 $self->_dbpnt( "Attaching diff") if $self->{verbose} > 2;
1744             print $out "\n--$self->{boundary}\n",
1745             "Content-Disposition: attachment; filename=",
1746             "r$self->{revision}-$self->{user}.diff\n",
1747             "Content-Type: $self->{diff_content_type}; charset=$self->{encoding}\n",
1748 155 100       8592 ($self->{language} ? "Content-Language: $self->{language}\n" : ()),
1749             "Content-Transfer-Encoding: 8bit\n\n";
1750 155         7792 $self->_dump_diff($out, $diff);
1751             }
1752              
1753             ##############################################################################
1754              
1755             =head3 end_message
1756              
1757             $notifier->end_message($file_handle);
1758              
1759             Outputs the final part of the message,. In this case, that means only a
1760             boundary if the C parameter is true. Designed to be called after
1761             any call to C.
1762              
1763             =cut
1764              
1765             sub end_message {
1766 2633     2633 1 10566 my ($self, $out) = @_;
1767 2633 100       12153 print $out "--$self->{boundary}--\n" if $self->{attach_diff};
1768 2633         5636 return $self;
1769             }
1770              
1771             ##############################################################################
1772              
1773             =head3 run_ticket_map
1774              
1775             $notifier->run_ticket_map( \&callback, @params );
1776              
1777             Loops over the ticket systems you have defined, calling the C<$callback>
1778             function for each one, passing to it the regex, url and @params specified as
1779             its parameters.
1780              
1781             =cut
1782              
1783             sub run_ticket_map {
1784 65     65 1 790 my ($self, $callback, @params) = @_;
1785              
1786             # Make ticketing system links.
1787 65 50       746 my $map = $self->ticket_map or return;
1788 65         544 my $has_header = 0;
1789 65         1462 while (my ($regex, $url) = each %$map) {
1790 332   66     4172 $regex = $_ticket_regexen{ $regex } || $regex;
1791 332         1917 $callback->( $regex, $url, @params );
1792             }
1793             }
1794              
1795             ##############################################################################
1796              
1797             =head3 run_filters
1798              
1799             $data = $notifier->run_filters( $output_type => $data );
1800              
1801             Runs the filters for C<$output_type> on $data. Used internally by SVN::Notify
1802             and by subclasses.
1803              
1804             =cut
1805              
1806             sub run_filters {
1807 38181     38181 1 202745 my ($self, $type, $data) = @_;
1808 38181 100       382083 my $filters = $self->{filters}{$type} or return $data;
1809 1329         18845 $data = $_->($self, $data) for @$filters;
1810 1329         2535222 return $data;
1811             }
1812              
1813             ##############################################################################
1814              
1815             =head3 filters_for
1816              
1817             my $filters = $notifier->filters_for( $output_type );
1818              
1819             Returns an array reference of of the filters loaded for C<$output_type>.
1820             Returns C if there are no filters have been loaded for C<$output_type>.
1821              
1822             =cut
1823              
1824             sub filters_for {
1825 3579     3579 1 17372 shift->{filters}{+shift};
1826             }
1827              
1828             ##############################################################################
1829              
1830             =head3 diff_handle
1831              
1832             my $diff = $notifier->diff_handle;
1833             while (<$diff>) { print }
1834              
1835             Returns a file handle reference providing access to the the commit diff. It
1836             will usually be passed as the second argument to C or
1837             C.
1838              
1839             =cut
1840              
1841             sub diff_handle {
1842 704     704 1 3559 my $self = shift;
1843             # To avoid svnlook output except for diff contents, such as "Modified"
1844             # etc., to be output in the localized string encoded with another encoding
1845             # from diff contents. HTML and HTML::ColorDiff also expect the terms
1846             # printed in English.
1847 704         15174 local $ENV{LANG} = 'C';
1848              
1849             return $self->_pipe(
1850             $self->{diff_encoding},
1851             '-|' => $self->{svnlook},
1852             'diff' => $self->{repos_path},
1853             '-r' => $self->{revision},
1854             ( $self->{diff_switches}
1855 48 50       2232 ? grep { defined && $_ ne '' }
1856             # Allow quoting of arguments, but strip out the quotes.
1857             split /(?:'([^']+)'|"([^"]+)")?\s+(?:'([^']+)'|"([^"]+)")?/,
1858             $self->{diff_switches}
1859             : ()
1860 704 100       10443 ),
1861             );
1862             }
1863              
1864             ##############################################################################
1865             # This method actually dumps the output of C. It's a separate
1866             # method because output_attached_diff() and output_diff() do essentially the
1867             # same thing, so they can both call it. The diff output will be truncated at
1868             # max_diff_length, if specified.
1869             ##############################################################################
1870              
1871             sub _dump_diff {
1872 383     383   3874 my ($self, $out, $diff) = @_;
1873 383         6148 $diff = $self->run_filters( diff => $diff );
1874              
1875 383 100       7573 if (my $max = $self->{max_diff_length}) {
1876 12         278 my $length = 0;
1877 12         7042695 while (<$diff>) {
1878 288         2560 s/[\n\r]+$//;
1879 288 100       922 if (($length += length) < $max) {
1880 276         1793 print $out $_, "\n";
1881             }
1882             else {
1883 12         250 print $out
1884             "\n\@\@ Diff output truncated at $max characters. \@\@\n";
1885 12         142 last;
1886             }
1887             }
1888             }
1889              
1890             else {
1891 371         184147153 while (<$diff>) {
1892 12765         47500208 s/[\n\r]+$//;
1893 12765         86548 print $out $_, "\n";
1894             }
1895             }
1896 383 50       16659 close $diff or warn "Child process exited: $?\n";
1897 383         8665 return $self;
1898             }
1899              
1900             ##############################################################################
1901              
1902             __PACKAGE__->_accessors(qw(
1903             repos_path
1904             revision
1905             to_regex_map
1906             to_email_map
1907             from
1908             user_domain
1909             svnlook
1910             sendmail
1911             set_sender
1912             add_headers
1913             smtp
1914             encoding
1915             diff_encoding
1916             svn_encoding
1917             env_lang
1918             svn_env_lang
1919             language
1920             with_diff
1921             attach_diff
1922             diff_switches
1923             reply_to
1924             subject_prefix
1925             subject_cx
1926             max_sub_length
1927             max_diff_length
1928             author_url
1929             revision_url
1930             ticket_url
1931             ticket_regex
1932             ticket_map
1933             header
1934             footer
1935             verbose
1936             boundary
1937             user
1938             date
1939             message
1940             message_size
1941             subject
1942             files
1943             ));
1944              
1945             ##############################################################################
1946             # This method is used to create accessors for the list of attributes passed to
1947             # it. It creates them both for SVN::Notify (just above) and for all subclasses
1948             # in register_attributes().
1949             ##############################################################################
1950              
1951             sub _accessors {
1952 562     562   1131 my $class = shift;
1953 562         1533 for my $attr (@_) {
1954 293     293   5466922 no strict 'refs';
  293         786  
  293         34262  
1955 12385         41125 *{"$class\::$attr"} = sub {
1956 32782     32782   81474 my $self = shift;
1957 32782 100       256347 return $self->{$attr} unless @_;
1958 53         308 $self->{$attr} = shift;
1959 53         297 return $self;
1960 12385         28815 };
1961             }
1962             }
1963              
1964             # Aliases for deprecated attributes.
1965 84     84 1 840 sub svnweb_url { shift->revision_url(@_) }
1966 84     84 1 1008 sub viewcvs_url { shift->revision_url(@_) }
1967 0     0 1 0 sub charset { shift->encoding(@_) }
1968              
1969             # Deprecated ticket URL systems.
1970             for my $tick (qw(rt bugzilla jira gnats)) {
1971 293     293   1570 no strict 'refs';
  293         435  
  293         28487  
1972             *{$tick . '_url'} = sub {
1973 156     156   520 my $self = shift;
1974 156   50     1456 my $map = $self->{ticket_map} || {};
1975 156 50       1716 return $map->{$tick} unless @_;
1976 0 0       0 if (my $url = shift) {
1977 0         0 $map->{$tick} = $url;
1978             } else {
1979 0         0 delete $map->{$tick};
1980             }
1981 0         0 $self->{ticket_map} = $map;
1982 0         0 return $self;
1983             };
1984             }
1985              
1986             for my $attr (qw(to strip_cx_regex)) {
1987 293     293   1863 no strict 'refs';
  293         553  
  293         210039  
1988             *{__PACKAGE__ . "::$attr"} = sub {
1989 117     117   967 my $self = shift;
1990 117 50       1432 return wantarray ? @{ $self->{$attr} } : $self->{$attr}[0] unless @_;
  117 50       2690  
1991 0         0 $self->{$attr}= \@_;
1992 0         0 return $self;
1993             };
1994             }
1995              
1996             =head2 Accessors
1997              
1998             =head3 repos_path
1999              
2000             my $repos_path = $notifier->repos_path;
2001             $notifier = $notifier->repos_path($repos_path);
2002              
2003             Gets or sets the value of the C attribute.
2004              
2005             =head3 revision
2006              
2007             my $revision = $notifier->revision;
2008             $notifier = $notifier->revision($revision);
2009              
2010             Gets or sets the value of the C attribute.
2011              
2012             =head3 to
2013              
2014             my $to = $notifier->to;
2015             $notifier = $notifier->to($to);
2016             my @tos = $notifier->to;
2017             $notifier = $notifier->to(@tos);
2018              
2019             Gets or sets the list of values stored in the C attribute. In a scalar
2020             context, it returns only the first value in the list, for backwards
2021             compatibility with older versions of SVN::Notify. In list context, it of
2022             course returns the entire list. Pass in one or more values to set all of the
2023             values for the C attribute.
2024              
2025             =head3 to_regex_map
2026              
2027             my $to_regex_map = $notifier->to_regex_map;
2028             $notifier = $notifier->to_regex_map($to_regex_map);
2029              
2030             Gets or sets the value of the C attribute, which is a hash
2031             reference of email addresses mapped to regular expressions.
2032              
2033             =head3 to_email_map
2034              
2035             my $to_email_map = $notifier->to_email_map;
2036             $notifier = $notifier->to_email_map($to_email_map);
2037              
2038             Gets or sets the value of the C attribute, which is a hash
2039             reference of regular expressions mapped to email addresses.
2040              
2041             =head3 from
2042              
2043             my $from = $notifier->from;
2044             $notifier = $notifier->from($from);
2045              
2046             Gets or sets the value of the C attribute.
2047              
2048             =head3 user_domain
2049              
2050             my $user_domain = $notifier->user_domain;
2051             $notifier = $notifier->user_domain($user_domain);
2052              
2053             Gets or sets the value of the C attribute.
2054              
2055             =head3 svnlook
2056              
2057             my $svnlook = $notifier->svnlook;
2058             $notifier = $notifier->svnlook($svnlook);
2059              
2060             Gets or sets the value of the C attribute.
2061              
2062             =head3 sendmail
2063              
2064             my $sendmail = $notifier->sendmail;
2065             $notifier = $notifier->sendmail($sendmail);
2066              
2067             Gets or sets the value of the C attribute.
2068              
2069             =head3 set_sender
2070              
2071             my $set_sender = $notifier->set_sender;
2072             $notifier = $notifier->set_sender($set_sender);
2073              
2074             Gets or sets the value of the C attribute.
2075              
2076             =head3 smtp
2077              
2078             my $smtp = $notifier->smtp;
2079             $notifier = $notifier->smtp($smtp);
2080              
2081             Gets or sets the value of the C attribute.
2082              
2083             =head3 encoding
2084              
2085             my $encoding = $notifier->encoding;
2086             $notifier = $notifier->encoding($encoding);
2087              
2088             Gets or sets the value of the C attribute. C is an alias
2089             preserved for backward compatibility.
2090              
2091             =head3 svn_encoding
2092              
2093             my $svn_encoding = $notifier->svn_encoding;
2094             $notifier = $notifier->svn_encoding($svn_encoding);
2095              
2096             Gets or sets the value of the C attribute.
2097              
2098             =head3 diff_encoding
2099              
2100             my $diff_encoding = $notifier->diff_encoding;
2101             $notifier = $notifier->diff_encoding($diff_encoding);
2102              
2103             Gets or sets the value of the C attribute.
2104              
2105             =head3 language
2106              
2107             my $language = $notifier->language;
2108             $notifier = $notifier->language($language);
2109              
2110             Gets or sets the value of the C attribute.
2111              
2112             =head3 env_lang
2113              
2114             my $env_lang = $notifier->env_lang;
2115             $notifier = $notifier->env_lang($env_lang);
2116              
2117             Gets or sets the value of the C attribute, which is set to C<<
2118             $notify->language . '.' . $notify->encoding >> when C is set, and
2119             otherwise is C. This attribute is used to set the C<$LANG> environment
2120             variable, if it is not already set by the environment, before executing
2121             C.
2122              
2123             =head3 svn_env_lang
2124              
2125             my $svn_env_lang = $notifier->svn_env_lang;
2126             $notifier = $notifier->svn_env_lang($svn_env_lang);
2127              
2128             Gets or sets the value of the C attribute, which is set to C<<
2129             $notify->language . '.' . $notify->svn_encoding >> when C is set,
2130             and otherwise is C. This attribute is used to set the C<$LANG>
2131             environment variable, if it is not already set by the environment, before
2132             executing C. It is not used for C, however, as the diff
2133             itself will be emitted in raw octets except for headers such as "Modified",
2134             which need to be in English so that subclasses can parse them. Thus, C<$LANG>
2135             is always set to "C" for the execution of C.
2136              
2137             =head3 with_diff
2138              
2139             my $with_diff = $notifier->with_diff;
2140             $notifier = $notifier->with_diff($with_diff);
2141              
2142             Gets or sets the value of the C attribute.
2143              
2144             =head3 attach_diff
2145              
2146             my $attach_diff = $notifier->attach_diff;
2147             $notifier = $notifier->attach_diff($attach_diff);
2148              
2149             Gets or sets the value of the C attribute.
2150              
2151             =head3 diff_switches
2152              
2153             my $diff_switches = $notifier->diff_switches;
2154             $notifier = $notifier->diff_switches($diff_switches);
2155              
2156             Gets or sets the value of the C attribute.
2157              
2158             =head3 reply_to
2159              
2160             my $reply_to = $notifier->reply_to;
2161             $notifier = $notifier->reply_to($reply_to);
2162              
2163             Gets or sets the value of the C attribute.
2164              
2165             =head3 add_headers
2166              
2167             my $add_headers = $notifier->add_headers;
2168             $notifier = $notifier->add_headers({
2169             'X-Accept' => [qw(This That)],
2170             'X-Reject' => 'Me!',
2171             });
2172              
2173             Gets or sets the value of the C attribute, which is a hash
2174             reference of the headers to be added to the email message. If one header needs
2175             to appear multiple times, simply pass the corresponding hash value as an array
2176             reference of each value for the header. Not to be confused with the C
2177             accessor, which gets and sets text to be included at the beginning of the body
2178             of the email message.
2179              
2180             =head3 subject_prefix
2181              
2182             my $subject_prefix = $notifier->subject_prefix;
2183             $notifier = $notifier->subject_prefix($subject_prefix);
2184              
2185             Gets or sets the value of the C attribute.
2186              
2187             =head3 subject_cx
2188              
2189             my $subject_cx = $notifier->subject_cx;
2190             $notifier = $notifier->subject_cx($subject_cx);
2191              
2192             Gets or sets the value of the C attribute.
2193              
2194             =head3 strip_cx_regex
2195              
2196             my $strip_cx_regex = $notifier->strip_cx_regex;
2197             $notifier = $notifier->strip_cx_regex($strip_cx_regex);
2198             my @strip_cx_regexs = $notifier->strip_cx_regex;
2199             $notifier = $notifier->strip_cx_regex(@strip_cx_regexs);
2200              
2201             Gets or sets the list of values stored in the C attribute. In
2202             a scalar context, it returns only the first value in the list; in list
2203             context, it of course returns the entire list. Pass in one or more values to
2204             set all of the values for the C attribute.
2205              
2206             =head3 max_sub_length
2207              
2208             my $max_sub_length = $notifier->max_sub_length;
2209             $notifier = $notifier->max_sub_length($max_sub_length);
2210              
2211             Gets or sets the value of the C attribute.
2212              
2213             =head3 max_diff_length
2214              
2215             my $max_diff_length = $notifier->max_diff_length;
2216             $notifier = $notifier->max_diff_length($max_diff_length);
2217              
2218             Gets or set the value of the C attribute.
2219              
2220             =head3 author_url
2221              
2222             my $author_url = $notifier->author_url;
2223             $notifier = $notifier->author_url($author_url);
2224              
2225             Gets or sets the value of the C attribute.
2226              
2227             =head3 revision_url
2228              
2229             my $revision_url = $notifier->revision_url;
2230             $notifier = $notifier->revision_url($revision_url);
2231              
2232             Gets or sets the value of the C attribute.
2233              
2234             =head3 svnweb_url
2235              
2236             Deprecated. Pleas use C, instead.
2237              
2238             =head3 viewcvs_url
2239              
2240             Deprecated. Pleas use C, instead.
2241              
2242             =head3 verbose
2243              
2244             my $verbose = $notifier->verbose;
2245             $notifier = $notifier->verbose($verbose);
2246              
2247             Gets or sets the value of the C attribute.
2248              
2249             =head3 boundary
2250              
2251             my $boundary = $notifier->boundary;
2252             $notifier = $notifier->boundary($boundary);
2253              
2254             Gets or sets the value of the C attribute. This string is normally
2255             set by a call to C, but may be set ahead of time.
2256              
2257             =head3 user
2258              
2259             my $user = $notifier->user;
2260             $notifier = $notifier->user($user);
2261              
2262             Gets or sets the value of the C attribute, which is set to the value
2263             pulled in from F by the call to C.
2264              
2265             =head3 date
2266              
2267             my $date = $notifier->date;
2268             $notifier = $notifier->date($date);
2269              
2270             Gets or sets the value of the C attribute, which is set to the value
2271             pulled in from F by the call to C.
2272              
2273             =head3 message
2274              
2275             my $message = $notifier->message;
2276             $notifier = $notifier->message($message);
2277              
2278             Gets or sets the value of the C attribute, which is set to an array
2279             reference of strings by the call to C.
2280              
2281             =head3 message_size
2282              
2283             my $message_size = $notifier->message_size;
2284             $notifier = $notifier->message_size($message_size);
2285              
2286             Gets or sets the value of the C attribute, which is set to the
2287             value pulled in from F by the call to C.
2288              
2289             =head3 subject
2290              
2291             my $subject = $notifier->subject;
2292             $notifier = $notifier->subject($subject);
2293              
2294             Gets or sets the value of the C attribute, which is normally set
2295             by a call to C, but may be set explicitly.
2296              
2297             =head3 files
2298              
2299             my $files = $notifier->files;
2300             $notifier = $notifier->files($files);
2301              
2302             Gets or sets the value of the C attribute, which is set to a hash
2303             reference of change type mapped to arrays of strings by the call to
2304             C.
2305              
2306             =head3 header
2307              
2308             my $header = $notifier->header;
2309             $notifier = $notifier->header($header);
2310              
2311             Gets or set the value of the C
attribute. Not to be confused with the
2312             C attribute, which manages headers to be inserted into the
2313             notification email message headers.
2314              
2315             =head3 footer
2316              
2317             my $footer = $notifier->footer;
2318             $notifier = $notifier->footer($footer);
2319              
2320             Gets or set the value of the C
attribute.
2321              
2322             =cut
2323              
2324             ##############################################################################
2325             # This method forks off a process to execute an external program and any
2326             # associated arguments and returns a file handle that can be read from to
2327             # fetch the output of the external program, or written to. Pass "-|" as the
2328             # sole argument to read from another process (such as svnlook), and pass "|-"
2329             # to write to another process (such as sendmail).
2330             ##############################################################################
2331              
2332             sub _pipe {
2333 9133     9133   54727 my ($self, $encode, $mode) = (shift, shift, shift);
2334             $self->_dbpnt( q{Piping execution of "} . join(q{" "}, @_) . q{"})
2335 9133 100       39146 if $self->{verbose};
2336             # Safer version of backtick (see perlipc(1)).
2337 9133         34804 local *PIPE;
2338 9133         15909 if (WIN32) {
2339             my $cmd = $mode eq '-|'
2340             ? q{"} . join(q{" "}, @_) . q{"|}
2341             : q{|"} . join(q{" "}, @_) . q{"};
2342             open PIPE, $cmd or die "Cannot fork: $!\n";
2343             binmode PIPE, ":encoding($encode)" if PERL58 && $encode;
2344             return *PIPE;
2345             }
2346              
2347 9133         9597741 my $pid = open PIPE, $mode;
2348 9133 50       251841 die "Cannot fork: $!\n" unless defined $pid;
2349              
2350 9133 100       212572 if ($pid) {
2351             # Parent process. Set the encoding layer and return the file handle.
2352 8848 50   285   1289010 binmode PIPE, ":encoding($encode)" if PERL58 && $encode;
  285         10210  
  285         2319  
  285         12427  
2353 8848         3619722 return *PIPE;
2354             } else {
2355             # Child process. Execute the commands.
2356 285 0       0 exec @_ or die "Cannot exec $_[0]: $!\n";
2357             # Not reached.
2358             }
2359             }
2360              
2361             ##############################################################################
2362             # This method passes its arguments to _pipe(), but then fetches each line
2363             # off output from the returned file handle, safely strips out and replaces any
2364             # newlines and carriage returns, and returns an array reference of those
2365             # lines.
2366             ##############################################################################
2367              
2368             sub _read_pipe {
2369 2793     2793   712160 my $self = shift;
2370 2793         17320 my $fh = $self->_pipe( $self->{svn_encoding}, '-|', @_ );
2371 2709         58358 local $/; my @lines = split /(?:\r\n|\r|\n)/, <$fh>;
  2709         1695260338  
2372 2709 50       1222946 close $fh or warn "Child process exited: $?\n";
2373 2709         69384 return \@lines;
2374             }
2375              
2376             ##############################################################################
2377             # This method is used for debugging output in various verbose modes.
2378             ##############################################################################
2379              
2380 0     0   0 sub _dbpnt { print ref(shift), ': ', join(' ', @_), "\n"; }
2381              
2382             ##############################################################################
2383             # This function is used to exit the program with an error if a parameter is
2384             # missing.
2385             ##############################################################################
2386              
2387             sub _usage {
2388 3     3   7 my ($msg) = @_;
2389              
2390             # Just die if the API is used.
2391 3 50       30 die $msg if $0 !~ /\bsvnnotify(?:[.]bat)?$/;
2392              
2393             # Otherwise, tell 'em how to use it.
2394 0         0 $msg =~ s/_/-/g;
2395 0         0 $msg =~ s/(\s+")/$1--/g;
2396 0         0 $msg =~ s/\bparameter\b/option/g;
2397 0         0 require Pod::Usage;
2398 0         0 Pod::Usage::pod2usage(
2399             '-message' => $msg,
2400             '-verbose' => 99,
2401             '-sections' => '(?i:(Usage|Options))',
2402             '-exitval' => 1,
2403             );
2404             }
2405              
2406             package SVN::Notify::SMTP;
2407              
2408             $SVN::Notify::SMTP::VERSION = '2.87';
2409              
2410             sub get_handle {
2411 4     4   32 my ($class, $notifier) = @_;
2412              
2413             # Load Net::SMTP::TLS.
2414 4         94 require Net::SMTP::TLS;
2415 4         2689 require Sys::Hostname;
2416             my $smtp = Net::SMTP::TLS->new(
2417             $notifier->{smtp},
2418             Hello => Sys::Hostname::hostname(),
2419             ( $notifier->{smtp_port} ? ( Port => $notifier->{smtp_port} ) : () ),
2420             ( $notifier->{smtp_tls} ? () : (NoTLS => 1) ),
2421             ( $notifier->{smtp_user} ? ( User => $notifier->{smtp_user} ) : () ),
2422             ( $notifier->{smtp_pass} ? ( Password => $notifier->{smtp_pass} ) : () ),
2423 4 100       4900 ( $notifier->{verbose} ? ( Debug => 1 ) : () )
    50          
    100          
    100          
    100          
    50          
2424             ) or die "Unable to create SMTP object: $!";
2425              
2426 4         390 $smtp->mail($notifier->{from});
2427 4         64 $smtp->to(map { split /\s*,\s*/ } @{ $notifier->{to} });
  8         162  
  4         23  
2428 4         64 $smtp->data;
2429 4         89 tie local(*SMTP), $class, $smtp, $notifier;
2430             # Perl 5.6 requires the escape.
2431 4         30 return SVN::Notify::PERL58 ? *SMTP : \*SMTP;
2432             }
2433              
2434             sub TIEHANDLE {
2435 4     4   27 my ($class, $smtp, $notifier) = @_;
2436 4         64 bless { smtp => $smtp, notifier => $notifier }, $class;
2437             }
2438              
2439             sub PRINT {
2440 44     44   106 my $self = shift;
2441 44         80 if (SVN::Notify::PERL58) {
2442 44         195 my $encode = $self->{notifier}->encoding;
2443             return $self->{smtp}->datasend( map {
2444 44         135 Encode::encode( $encode, $_ )
  196         7976  
2445             } @_ )
2446             }
2447 0         0 return $self->{smtp}->datasend(@_);
2448             }
2449              
2450             sub PRINTF {
2451 4     4   25 my $self = shift;
2452 4         45 $self->PRINT( sprintf(shift, @_) );
2453             }
2454              
2455             sub CLOSE {
2456 4     4   16 my $self = shift;
2457 4         30 $self->{smtp}->dataend;
2458 4         29 $self->{smtp}->quit;
2459             }
2460              
2461             1;
2462             __END__