File Coverage

blib/lib/SVN/Notify.pm
Criterion Covered Total %
statement 464 492 94.3
branch 228 290 78.6
condition 73 112 65.1
subroutine 54 57 94.7
pod 30 30 100.0
total 849 981 86.5


line stmt bran cond sub pod time code
1             package SVN::Notify;
2              
3 293     293   154140 use strict;
  293         391  
  293         10670  
4             require 5.006_000;
5 293     293   967 use constant WIN32 => $^O eq 'MSWin32';
  293         262  
  293         18191  
6 293     293   919 use constant PERL58 => $] > 5.007_000;
  293         288  
  293         102712  
7             require Encode if PERL58;
8             $SVN::Notify::VERSION = '2.86';
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 3970110 my ($class, %params) = @_;
685              
686             # Delegate to a subclass if requested.
687 3406 100       16598 if (my $handler = delete $params{handler}) {
688 346         1656 my $subclass = __PACKAGE__ . "::$handler";
689 346 50       1093 unless ($subclass eq $class) {
690 346 50       23801 eval "require $subclass" or die $@;
691 346         4221 return $subclass->new(%params);
692             }
693             }
694              
695             # Load any filters.
696 3060   100     36065 $params{filters} ||= {};
697 3060 100       12754 if (ref $params{filters} eq 'ARRAY') {
698 716         1186 my $filts = {};
699 716         1257 for my $pkg ( @{ $params{filters} } ) {
  716         2414  
700 778 100       5358 $pkg = "SVN::Notify::Filter::$pkg" if $pkg !~ /::/;
701 778 100       3211 if ($filters{$pkg}) {
702 308         572 while (my ($k, $v) = each %{ $filters{$pkg} }) {
  876         3692  
703 568   50     2974 $filts->{$k} ||= [];
704 568         620 push @{ $filts->{$k} }, $v;
  568         1179  
705             }
706             } else {
707 470 50       37138 eval "require $pkg" or die $@;
708 470         2020 $filters{$pkg} = {};
709 293     293   1150 no strict 'refs';
  293         331  
  293         733800  
710 470         738 while ( my ($k, $v) = each %{ "$pkg\::" } ) {
  1355         9942  
711 885 100       830 my $code = *{$v}{CODE} or next;
  885         4085  
712 769         1705 $filters{$pkg}->{$k} = $code;
713 769   100     4848 $filts->{$k} ||= [];
714 769         801 push @{ $filts->{$k} }, $code;
  769         2361  
715             }
716             }
717             }
718 716         1545 $params{filters} = $filts;
719             }
720              
721             # Make sure that the tos are an arrayref.
722 3060 100 66     13967 $params{to} = [ $params{to} || () ] unless ref $params{to};
723              
724             # Check for required parameters.
725             $class->_dbpnt( "Checking required parameters to new()")
726 3060 100       8165 if $params{verbose};
727             _usage( qq{Missing required "repos_path" parameter} )
728 3060 100       7841 unless $params{repos_path};
729             _usage( qq{Missing required "revision" parameter} )
730 3059 100       9331 unless $params{revision};
731              
732             # Set up default values.
733 3058   0     9418 $params{svnlook} ||= $ENV{SVNLOOK} || $class->find_exe('svnlook');
      33        
734 3058   100     18882 $params{with_diff} ||= $params{attach_diff};
735 3058   100     18342 $params{verbose} ||= 0;
736 3058   50     24896 $params{encoding} ||= $params{charset} || 'UTF-8';
      66        
737 3058   66     13497 $params{svn_encoding} ||= $params{encoding};
738 3058   66     12806 $params{diff_encoding} ||= $params{svn_encoding};
739 3058   50     20580 $params{diff_content_type} ||= $params{diff_content_type} || 'text/plain';
      66        
740             $params{sendmail} ||= $ENV{SENDMAIL} || $class->find_exe('sendmail')
741 3058 100 0     10932 unless $params{smtp};
      33        
742              
743             _usage( qq{Cannot find sendmail and no "smtp" parameter specified} )
744 3058 50 66     6841 unless $params{sendmail} || $params{smtp};
745              
746             # Set up the environment locale.
747 3058 100 66     10670 if ( $params{language} && !$ENV{LANG} ) {
748 210         822 ( my $lang_country = $params{language} ) =~ s/-/_/g;
749 210         756 for my $p (qw(encoding svn_encoding)) {
750 420         791 my $encoding = $params{$p};
751 420 50       1438 $encoding =~ s/-//g if uc($encoding) ne 'UTF-8';
752 420         2803 (my $label = $p ) =~ s/(_?)encoding/$1/;
753 420         1884 $params{"${label}env_lang"} = "$lang_country.$encoding";
754             }
755             }
756              
757             # Set up the revision URL.
758             $params{revision_url} ||= delete $params{svnweb_url}
759 3058   66     22415 || delete $params{viewcvs_url};
      100        
760 3058 50 66     13501 if ($params{revision_url} && $params{revision_url} !~ /%s/) {
761 0         0 warn "--revision-url must have '%s' format\n";
762 0         0 $params{revision_url} .= '/revision/?rev=%s&view=rev'
763             }
764              
765             # Set up the issue tracking links.
766 3058         6457 my $track = $params{ticket_map};
767 3058 100       7067 if ($params{ticket_regex}) {
768 77         408 $track->{ delete $params{ticket_regex} } = delete $params{ticket_url};
769             }
770              
771 3058         7119 for my $system (qw(rt bugzilla jira gnats)) {
772 12232         13674 my $param = $system . '_url';
773 12232 100       21157 if ($params{ $param }) {
774 283         493 $track->{ $system } = delete $params{ $param };
775             warn "--$system-url must have '%s' format\n"
776 283 50       885 unless $track->{ $system } =~ /%s/;
777             }
778             }
779 3058 100       6594 $params{ticket_map} = $track if $track;
780              
781             # Make it so!
782 3058 100       6815 $class->_dbpnt( "Instantiating $class object") if $params{verbose};
783 3058         21168 return bless \%params, $class;
784             }
785              
786             ##############################################################################
787              
788             =head2 Class Methods
789              
790             =head3 content_type
791              
792             my $content_type = SVN::Notify->content_type;
793              
794             Returns the content type of the notification message, "text/plain". Used to
795             set the Content-Type header for the message.
796              
797             =cut
798              
799 1617     1617 1 6139 sub content_type { 'text/plain' }
800              
801             ##############################################################################
802              
803             =head3 register_attributes
804              
805             SVN::Notify::Subclass->register_attributes(
806             foo_attr => 'foo-attr=s',
807             bar => 'bar',
808             bat => undef,
809             );
810              
811             This class method is used by subclasses to register new attributes. Pass in a
812             list of key/value pairs, where the keys are the attribute names and the values
813             are option specifications in the format required by Getopt::Long. SVN::Notify
814             will create accessors for each attribute, and if the corresponding value is
815             defined, it will be used by the C class method to get a
816             command-line option value.
817              
818             See for an example usage of
819             C.
820              
821             =cut
822              
823             my %OPTS;
824              
825             sub register_attributes {
826 269     269 1 554 my $class = shift;
827 269         387 my @attrs;
828 269         992 while (@_) {
829 665         666 push @attrs, shift;
830 665 50       1239 if (my $opt = shift) {
831 665         1634 $OPTS{$attrs[-1]} = $opt;
832             }
833             }
834 269         1241 $class->_accessors(@attrs);
835             }
836              
837             ##############################################################################
838              
839             =head3 get_options
840              
841             my $options = SVN::Notify->get_options;
842              
843             Parses the command-line options in C<@ARGV> to a hash reference suitable for
844             passing as the parameters to C. See L<"new"> for a complete list of the
845             supported parameters and their corresponding command-line options.
846              
847             This method use Getopt::Long to parse C<@ARGV>. It then looks for any
848             C and C options and, if it finds any, loads the appropriate
849             classes and parses any options they requires from C<@ARGV>. Subclasses and
850             filter classes should use C to register any attributes
851             and options they require.
852              
853             After that, on Perl 5.8 and later, it decodes all of the string option from
854             the encoding specified by the C option or UTF-8. This allows options
855             to be passed to SVN::Notify in that encoding and end up being displayed
856             properly in the resulting notification message.
857              
858             =cut
859              
860             sub get_options {
861 4     4 1 23633 my $class = shift;
862 4         5 my $opts = {};
863 4         779 require Getopt::Long;
864              
865             # Enable bundling and, at the same time, case-sensitive matching of
866             # single character options. Also enable pass-through so that subclasses
867             # can grab more options.
868 4         7288 Getopt::Long::Configure (qw(bundling pass_through));
869              
870             # Get options.
871             Getopt::Long::GetOptions(
872             'repos-path|p=s' => \$opts->{repos_path},
873             'revision|r=s' => \$opts->{revision},
874             'to|t=s@' => \$opts->{to},
875             'to-regex-map|x=s%' => \$opts->{to_regex_map},
876             'to-email-map=s%' => \$opts->{to_email_map},
877             'from|f=s' => \$opts->{from},
878             'user-domain|D=s' => \$opts->{user_domain},
879             'svnlook|l=s' => \$opts->{svnlook},
880             'sendmail|s=s' => \$opts->{sendmail},
881             'set-sender|E' => \$opts->{set_sender},
882             'smtp=s' => \$opts->{smtp},
883             'smtp-port=i' => \$opts->{smtp_port},
884             'smtp-tls!' => \$opts->{smtp_tls},
885             'encoding|charset|c=s'=> \$opts->{encoding},
886             'diff-encoding=s' => \$opts->{diff_encoding},
887             'svn-encoding=s' => \$opts->{svn_encoding},
888             'language|g=s' => \$opts->{language},
889             'with-diff|d' => \$opts->{with_diff},
890             'attach-diff|a' => \$opts->{attach_diff},
891             'diff-switches|w=s' => \$opts->{diff_switches},
892             'diff-content-type=s' => \$opts->{diff_content_type},
893             'reply-to|R=s' => \$opts->{reply_to},
894             'subject-prefix|P=s' => \$opts->{subject_prefix},
895             'subject-cx|C' => \$opts->{subject_cx},
896             'strip-cx-regex|X=s@' => \$opts->{strip_cx_regex},
897             'no-first-line|O' => \$opts->{no_first_line},
898             'max-sub-length|i=i' => \$opts->{max_sub_length},
899             'max-diff-length|e=i' => \$opts->{max_diff_length},
900             'handler|H=s' => \$opts->{handler},
901             'filter|F=s@' => \$opts->{filters},
902             'author-url|A=s' => \$opts->{author_url},
903             'ticket-regex=s' => \$opts->{ticket_regex},
904             'ticket-map=s%' => \$opts->{ticket_map},
905             'verbose|V+' => \$opts->{verbose},
906             'help|h' => \$opts->{help},
907             'man|m' => \$opts->{man},
908             'version|v' => \$opts->{version},
909             'header=s' => \$opts->{header},
910             'footer=s' => \$opts->{footer},
911             'smtp-user=s' => \$opts->{smtp_user},
912             'smtp-pass=s' => \$opts->{smtp_pass},
913             'smtp-authtype=s' => \$opts->{smtp_authtype},
914             'add-header=s%' => sub {
915 6     6   10569 shift; push @{ $opts->{add_headers}{+shift} }, shift
  6         5  
  6         21  
916             },
917             'revision-url|U|svnweb-url|S|viewcvs-url=s' => \$opts->{revision_url},
918             'rt-url|T|bugzilla-url|B|jira-url|J|gnats-url|G|ticket-url=s'
919             => \$opts->{ticket_url},
920 4 50       207 ) or return;
921              
922             # Load a subclass if one has been specified.
923 4 100       1549 if (my $hand = $opts->{handler}) {
924 1 50       61 eval "require " . __PACKAGE__ . "::$hand" or die $@;
925 1 50       5 if ($hand eq 'Alternative') {
926             # Load the alternative subclasses.
927             Getopt::Long::GetOptions(
928 0         0 map { delete $OPTS{$_} => \$opts->{$_} } keys %OPTS
  0         0  
929             );
930 0 0       0 for my $alt (@{ $opts->{alternatives} || ['HTML']}) {
  0         0  
931 0 0       0 eval "require " . __PACKAGE__ . "::$alt" or die $@;
932             }
933             }
934             }
935              
936             # Load any filters.
937 4 100       13 if ($opts->{filters}) {
938 1         2 for my $pkg ( @{ $opts->{filters} } ) {
  1         3  
939 1 50       5 $pkg = "SVN::Notify::Filter::$pkg" if $pkg !~ /::/;
940 1 50       63 eval "require $pkg" or die $@;
941             }
942             }
943              
944             # Disallow pass-through so that any invalid options will now fail.
945 4         11 Getopt::Long::Configure (qw(no_pass_through));
946 4         141 my @to_decode;
947 4 100       9 if (%OPTS) {
948             # Get a list of string options we'll need to decode.
949 2         7 @to_decode = map { $OPTS{$_} } grep { /=s$/ } keys %OPTS
  0         0  
  4         8  
950             if PERL58;
951              
952             # Load any other options.
953             Getopt::Long::GetOptions(
954 2         4 map { delete $OPTS{$_} => \$opts->{$_} } keys %OPTS
  4         12  
955             );
956             } else {
957             # Call GetOptions() again so that invalid options will be properly
958             # caught.
959 2         4 Getopt::Long::GetOptions();
960             }
961              
962 4         305 if (PERL58) {
963             # Decode all string options.
964 4   50     19 my $encoding = $opts->{encoding} || 'UTF-8';
965 4         12 for my $opt ( qw(
966             repos_path
967             revision
968             from
969             user_domain
970             svnlook
971             sendmail
972             smtp
973             smtp_tls
974             smtp_port
975             diff_switches
976             reply_to
977             subject_prefix
978             handler
979             author_url
980             ticket_regex
981             header
982             footer
983             smtp_user
984             smtp_pass
985             revision_url
986             ticket_url
987             ), @to_decode ) {
988             $opts->{$opt} = Encode::decode( $encoding, $opts->{$opt} )
989 84 100       805 if $opts->{$opt};
990             }
991             }
992              
993             # Clear the extra options specifications and return.
994 4         33 %OPTS = ();
995 4         18 return $opts;
996             }
997              
998             ##############################################################################
999              
1000             =head3 file_label_map
1001              
1002             my $map = SVN::Notify->file_label_map;
1003              
1004             Returns a hash reference of the labels to be used for the lists of files. A
1005             hash reference of file lists is stored in the C attribute after
1006             C has been called. The hash keys in that list correspond to
1007             Subversion status codes, and these are mapped to their appropriate labels by
1008             the hash reference returned by this method:
1009              
1010             { U => 'Modified Paths',
1011             A => 'Added Paths',
1012             D => 'Removed Paths',
1013             _ => 'Property Changed'
1014             }
1015              
1016             =cut
1017              
1018 2662     2662 1 5206 sub file_label_map { \%map }
1019              
1020             ##############################################################################
1021              
1022             =head3 find_exe
1023              
1024             my $exe = SVN::Notify->find_exe($exe_name);
1025              
1026             This method searches through the system path, as well as the extra directories
1027             F and F (because they're common paths for
1028             C and C for an executable file with the name C<$exe_name>.
1029             The first one it finds is returned with its full path. If none is found,
1030             C returns undef.
1031              
1032             =cut
1033              
1034             sub find_exe {
1035 1     1 1 4 my ($class, $exe) = @_;
1036 1         2 $exe .= '.exe' if WIN32;
1037 1         7 require File::Spec;
1038 1         3 require Config;
1039 1         8 for my $path (
1040             File::Spec->path,
1041             qw(/usr/local/bin /usr/bin /usr/sbin),
1042             'C:\\program files\\subversion\\bin',
1043             $Config::Config{installbin},
1044             $Config::Config{installscript},
1045             ) {
1046 3         38 my $file = File::Spec->catfile($path, $exe);
1047 3 100 66     61 return $file if -f $file && -x _;
1048             }
1049 0         0 return;
1050             }
1051              
1052             ##############################################################################
1053              
1054             =head1 Instance Interface
1055              
1056             =head2 Instance Methods
1057              
1058             =head3 prepare
1059              
1060             $notifier->prepare;
1061              
1062             Prepares the SVN::Notify object, collecting all the data it needs in
1063             preparation for sending the notification email. Really it's just a shortcut
1064             for:
1065              
1066             $notifier->prepare_recipients;
1067             $notifier->prepare_contents;
1068             $notifier->prepare_files;
1069             $notifier->prepare_subject;
1070              
1071             Only it returns after the call to C if there are no
1072             recipients (that is, as when recipients are specified solely by the
1073             C or C parameter and none of the regular
1074             expressions match any of the affected directories).
1075              
1076             =cut
1077              
1078             sub prepare {
1079 2714     2714 1 6051 my $self = shift;
1080 2714         8457 $self->run_filters('pre_prepare');
1081             _usage(
1082             qq{Missing required "to", "to_regex_map", or "to_email_map" parameter}
1083 2714 50 66     2921 ) unless @{$self->{to}} || $self->{to_regex_map} || $self->{to_email_map};
  2714   33     9495  
1084 2713         11822 $self->prepare_recipients;
1085 2707 50       3618 return $self unless @{ $self->{to} };
  2707         8616  
1086 2707         9646 $self->prepare_contents;
1087 2624         19660 $self->prepare_files;
1088 2541         21690 $self->prepare_subject;
1089 2541         6584 $self->run_filters('post_prepare');
1090 2541         35977 return $self;
1091             }
1092              
1093             ##############################################################################
1094              
1095             =head3 prepare_recipients
1096              
1097             $notifier->prepare_recipients;
1098              
1099             Collects and prepares a list of the notification recipients. The recipients
1100             are a combination of the value passed to the C parameter as well as any
1101             email addresses specified as keys in the hash reference passed C
1102             parameter or values passed to the C parameter, where the
1103             corresponding regular expressions stored in the hash matches one or more of
1104             the names of the directories affected by the commit.
1105              
1106             If the F parameter to C has a true value,
1107             C also determines the directory name to use for the
1108             context.
1109              
1110             =cut
1111              
1112             sub prepare_recipients {
1113 2799     2799 1 5056 my $self = shift;
1114 2799 100       8355 $self->_dbpnt( "Preparing recipients list") if $self->{verbose};
1115 2799 100 66     23097 unless (
      66        
1116             $self->{to_regex_map}
1117             || $self->{subject_cx}
1118             || $self->{to_email_map}
1119             ) {
1120 2493         6361 $self->{to} = $self->run_filters( recipients => $self->{to} );
1121 2493         4121 return $self;
1122             }
1123              
1124             # Prevent duplication.
1125 306         991 my $tos = $self->{to} = [ @{ $self->{to} } ];
  306         1327  
1126              
1127             my $regexen = $self->{to_regex_map} && $self->{to_email_map}
1128 0         0 ? [ %{ $self->{to_regex_map} }, reverse %{ $self->{to_email_map } } ]
  0         0  
1129 75         375 : $self->{to_regex_map} ? [ %{ $self->{to_regex_map} } ]
1130 306 100 66     2716 : $self->{to_email_map} ? [ reverse %{ $self->{to_email_map } } ]
  71 100       284  
    50          
1131             : undef;
1132              
1133 306 100       688 if ($regexen) {
1134             $self->_dbpnt( "Compiling regex_map regular expressions")
1135 146 50       442 if $self->{verbose} > 1;
1136 146         813 for (my $i = 1; $i < @$regexen; $i += 2) {
1137 438 50       809 $self->_dbpnt( qq{Compiling "$_"}) if $self->{verbose} > 2;
1138             # Remove initial slash and compile.
1139 438         1097 $regexen->[$i] =~ s|^\^[/\\]|^|;
1140 438         5047 $regexen->[$i] = qr/$regexen->[$i]/;
1141             }
1142             } else {
1143 160         520 $regexen = [];
1144             }
1145              
1146 306 50       838 local $ENV{LANG} = "$self->{svn_env_lang}" if $self->{svn_env_lang};
1147             my $fh = $self->_pipe(
1148             $self->{svn_encoding},
1149             '-|', $self->{svnlook},
1150             'changed',
1151             $self->{repos_path},
1152             '-r', $self->{revision},
1153 306         1767 );
1154              
1155             # Read in a list of the files changed.
1156 300         2034 my ($cx, %seen);
1157 300         191187878 while (<$fh>) {
1158 4993         28117 s/^.\s*//;
1159 4993         19915 s/[\n\r\/\\]+$//;
1160 4993         10315 for (my $i = 0; $i < @$regexen; $i += 2) {
1161 8640         6110 my ($email, $rx) = @{$regexen}[$i, $i + 1];
  8640         8212  
1162             # If the file matches the regex, save the email.
1163 8640 100       30330 if (/$rx/) {
1164 2448 50       3386 $self->_dbpnt( qq{"$_" matched $rx}) if $self->{verbose} > 2;
1165 2448 100       6488 push @$tos, $email unless $seen{$email}++;
1166             }
1167             }
1168             # Grab the context if it's needed for the subject.
1169 4993 100       14284 if ($self->{subject_cx}) {
1170             # XXX Do we need to set utf8 here?
1171 2113         3043 my $l = length;
1172 2113   66     4989 $cx ||= $_;
1173 2113   100     64872 $cx =~ s{[/\\]?[^/\\]+$}{} until !$cx || m{^\Q$cx\E(?:$|/|\\)};
1174             }
1175             }
1176             $self->_dbpnt( qq{Context is "$cx"})
1177 300 50 66     1980 if $self->{subject_cx} && $self->{verbose} > 1;
1178 300 50       15221 close $fh or warn "Child process exited: $?\n";
1179 300         1608 $self->{cx} = $cx;
1180 300         3030 $tos = $self->run_filters( recipients => $tos );
1181             $self->_dbpnt( 'Recipients: "', join(', ', @$tos), '"')
1182 300 50       1319 if $self->{verbose} > 1;
1183 300         4137 return $self;
1184             }
1185              
1186             ##############################################################################
1187              
1188             =head3 prepare_contents
1189              
1190             $notifier->prepare_contents;
1191              
1192             Prepares the contents of the commit message, including the name of the user
1193             who triggered the commit (and therefore the contents of the "From" header to
1194             be used in the email) and the log message.
1195              
1196             =cut
1197              
1198             sub prepare_contents {
1199 2793     2793 1 4093 my $self = shift;
1200 2793 100       7647 $self->_dbpnt( "Preparing contents") if $self->{verbose};
1201 2793 100       8323 local $ENV{LANG} = "$self->{svn_env_lang}" if $self->{svn_env_lang};
1202             my $lines = $self->_read_pipe($self->{svnlook}, 'info', $self->{repos_path},
1203 2793         13523 '-r', $self->{revision});
1204 2709         18870 $self->{user} = shift @$lines;
1205 2709         11705 $self->{date} = shift @$lines;
1206 2709         11098 $self->{message_size} = shift @$lines;
1207 2709         7354 $self->{message} = $lines;
1208              
1209             # Set up the from address.
1210 2709 50       9860 unless ($self->{from}) {
1211             $self->{from} = $self->{user}
1212 2709 100       22325 . ( $self->{user_domain} ? "\@$self->{user_domain}" : '' );
1213             }
1214 2709         23458 $self->{from} = $self->run_filters( from => $self->{from} );
1215              
1216 2709 100       11010 if ($self->{verbose} > 1) {
1217 2         36 $self->_dbpnt( "From: $self->{from}");
1218 2         24 $self->_dbpnt( "Message: @$lines");
1219             }
1220 2709         13184 return $self;
1221             }
1222              
1223             ##############################################################################
1224              
1225             =head3 prepare_files
1226              
1227             $notifier->prepare_files;
1228              
1229             Prepares the lists of files affected by the commit, sorting them into their
1230             categories: modified files, added files, and deleted files. It also compiles a
1231             list of files wherein a property was set, which might have some overlap with
1232             the list of modified files (if a single commit both modified a file and set a
1233             property on it).
1234              
1235             If the C parameter was specified and a single file was affected by
1236             the commit, then C will also specify that file name as the
1237             context to be used in the subject line of the commit email.
1238              
1239             =cut
1240              
1241             sub prepare_files {
1242 2709     2709 1 5467 my $self = shift;
1243 2709 100       9460 $self->_dbpnt( "Preparing file lists") if $self->{verbose};
1244 2709         4028 my %files;
1245 2709 100       9201 local $ENV{LANG} = "$self->{svn_env_lang}" if $self->{svn_env_lang};
1246             my $fh = $self->_pipe(
1247             $self->{svn_encoding},
1248             '-|', $self->{svnlook},
1249             'changed',
1250             $self->{repos_path},
1251             '-r', $self->{revision},
1252 2709         16362 );
1253              
1254             # Read in a list of changed files.
1255 2625         1943490509 my $cx = $_ = <$fh>;
1256 2625         100874 do {
1257 46761         210091 s/[\n\r]+$//;
1258 46761 50       158304 if (s/^(.)(.)\s+//) {
1259 46761 50       73609 $self->_dbpnt( "$1,$2 => $_") if $self->{verbose} > 2;
1260 46761         31427 push @{$files{$1}}, $_;
  46761         153531  
1261 46761 100 100     458784 push @{$files{_}}, $_ if $2 ne ' ' && $1 ne '_';
  2404         13590  
1262             }
1263             } while (<$fh>);
1264              
1265 2625 100 100     14474 if ($self->{subject_cx} && $. == 1) {
1266             # There's only one file; it's the context.
1267 51         714 $cx =~ s/[\n\r]+$//;
1268 51         408 ($self->{cx} = $cx) =~ s/^..\s+//;
1269             $self->_dbpnt( qq{File context is "$self->{cx}"})
1270 51 50       357 if $self->{verbose} > 1;
1271             }
1272             # Wait till we get here to close the file handle, otherwise $. gets reset
1273             # to 0!
1274 2625 50       129198 close $fh or warn "Child process exited: $?\n";
1275 2625         15144 $self->{files} = \%files;
1276 2625         24511 return $self;
1277             }
1278              
1279             ##############################################################################
1280              
1281             =head3 prepare_subject
1282              
1283             $notifier->prepare_subject;
1284              
1285             Prepares the subject line for the notification email. This method B be
1286             called after C and C, since each of
1287             those methods potentially sets up the context for use in the the subject
1288             line. The subject may have a prefix defined by the C parameter
1289             to C, it has the revision number, it might have the context if the
1290             C specified a true value, and it will have the first sentence or
1291             line of the commit, whichever is shorter. The subject may then be truncated to
1292             the maximum length specified by the C parameter.
1293              
1294             =cut
1295              
1296             sub prepare_subject {
1297 2625     2625 1 5429 my $self = shift;
1298 2625 100       9637 $self->_dbpnt( "Preparing subject") if $self->{verbose};
1299              
1300 2625         9449 $self->{subject} = '';
1301              
1302             # Start with the optional message and revision number..
1303 2625 100       10057 if ( defined $self->{subject_prefix} ) {
1304 127 100       1232 if ( index($self->{subject_prefix}, '%d') > 0 ) {
1305             $self->{subject} .=
1306 59         1770 sprintf $self->{subject_prefix}, $self->{revision};
1307             } else {
1308             $self->{subject} .=
1309 68         500 $self->{subject_prefix} . "[$self->{revision}] ";
1310             }
1311             } else {
1312 2498         10435 $self->{subject} .= "[$self->{revision}] ";
1313             }
1314              
1315             # Add the context if there is one.
1316 2625 100       8471 if ($self->{cx}) {
1317 148 100       903 if (my $rx = $self->{strip_cx_regex}) {
1318 42         1327 $self->{cx} =~ s/$_// for @$rx;
1319             }
1320 148 100       814 my $space = $self->{no_first_line} ? '' : ': ';
1321 148 50       1036 $self->{subject} .= $self->{cx} . $space if $self->{cx};
1322             }
1323              
1324             # Add the first sentence/line from the log message.
1325 2625 100       10565 unless ($self->{no_first_line}) {
1326             # Truncate to first period after a minimum of 10 characters.
1327 2583         12811 my $min = length $self->{message}[0];
1328 2583 50       10725 $min = 10 if $min > 10;
1329 2583         24829 my $i = index substr($self->{message}[0], $min), '. ';
1330             $self->{subject} .= $i > 0
1331             ? substr($self->{message}[0], 0, $i + 11)
1332 2583 100       17984 : $self->{message}[0];
1333             }
1334              
1335             # Truncate to the last word under 72 characters.
1336             $self->{subject} =~ s/^(.{0,$self->{max_sub_length}})\s+.*$/$1/m
1337             if $self->{max_sub_length}
1338 2625 100 66     14100 && length $self->{subject} > $self->{max_sub_length};
1339              
1340             # Now filter it.
1341 2625         19270 $self->{subject} = $self->run_filters( subject => $self->{subject} );
1342 2625 100       8394 $self->_dbpnt( qq{Subject is "$self->{subject}"}) if $self->{verbose};
1343              
1344 2625         4703 return $self;
1345             }
1346              
1347             ##############################################################################
1348              
1349             =head3 execute
1350              
1351             $notifier->execute;
1352              
1353             Sends the notification message. This involves opening a file handle to
1354             F or a tied file handle connected to an SMTP server and passing it
1355             to C. This is the main method used to send notifications or execute
1356             any other actions in response to Subversion activity.
1357              
1358             =cut
1359              
1360             sub execute {
1361 2625     2625 1 6485 my $self = shift;
1362 2625 100       9710 $self->_dbpnt( "Sending message") if $self->{verbose};
1363 2625         6535 $self->run_filters('pre_execute');
1364 2625 50       3595 return $self unless @{ $self->{to} };
  2625         13644  
1365              
1366 2625 100       7805 my $out = $self->{smtp} ? SVN::Notify::SMTP->get_handle($self) : do {
1367 2621 100       8322 local $ENV{LANG} = $self->{env_lang} if $self->{env_lang};
1368             $self->_pipe(
1369             $self->{encoding},
1370             '|-', $self->{sendmail},
1371             '-oi', '-t',
1372 2621 50       19034 ($self->{set_sender} ? ('-f', $self->{from}) : ())
1373             );
1374             };
1375              
1376             # Output the message.
1377 2543         49518 $self->output($out);
1378              
1379 2514 50       1536837018 close $out or warn "Child process exited: $?\n";
1380 2514 100       26356 $self->_dbpnt( 'Message sent' ) if $self->{verbose};
1381 2514         17319 $self->run_filters('post_execute');
1382 2514         54841 return $self;
1383             }
1384              
1385             ##############################################################################
1386              
1387             =head3 output
1388              
1389             $notifier->output($file_handle);
1390             $notifier->output($file_handle, $no_headers);
1391              
1392             Called internally by C to output a complete email message. The file
1393             a file handle, so that C and its related methods can print directly
1394             to the email message. The optional second argument, if true, will suppress the
1395             output of the email headers.
1396              
1397             Really C is a simple wrapper around a number of other method calls.
1398             It is thus essentially a shortcut for:
1399              
1400             $notifier->output_headers($out) unless $no_headers;
1401             $notifier->output_content_type($out);
1402             $notifier->start_body($out);
1403             $notifier->output_metadata($out);
1404             $notifier->output_log_message($out);
1405             $notifier->output_file_lists($out);
1406             if ($notifier->with_diff) {
1407             my $diff_handle = $self->diff_handle;
1408             if ($notifier->attach_diff) {
1409             $notifier->end_body($out);
1410             $notifier->output_attached_diff($out, $diff_handle);
1411             } else {
1412             $notifier->output_diff($out, $diff_handle);
1413             $notifier->end_body($out);
1414             }
1415             } else {
1416             $notifier->end_body($out);
1417             }
1418             $notifier->end_message($out);
1419              
1420             =cut
1421              
1422             sub output {
1423 2662     2662 1 13053 my ($self, $out, $no_headers) = @_;
1424 2662 100       13639 $self->_dbpnt( "Outputting notification message") if $self->{verbose} > 1;
1425 2662 100       30509 $self->output_headers($out) unless $no_headers;
1426 2662         25927 $self->output_content_type($out);
1427 2662         12824 $self->start_body($out);
1428 2662         13249 $self->output_metadata($out);
1429 2662         17054 $self->output_log_message($out);
1430 2662         13233 $self->output_file_lists($out);
1431 2662 100       7154 if ($self->{with_diff}) {
1432             # Get a handle on the diff output.
1433 704         5487 my $diff = $self->diff_handle;
1434 675 100       10259 if ($self->{attach_diff}) {
1435 155         4315 $self->end_body($out);
1436 155         6688 $self->output_attached_diff($out, $diff);
1437             } else {
1438 520         7818 $self->output_diff($out, $diff);
1439 520         4260 $self->end_body($out);
1440             }
1441             } else {
1442 1958         7359 $self->end_body($out);
1443             }
1444 2633         11889 $self->end_message($out);
1445              
1446 2633         10057 return $self;
1447             }
1448              
1449             ##############################################################################
1450              
1451             =head3 output_headers
1452              
1453             $notifier->output_headers($file_handle);
1454              
1455             Outputs the headers for the notification message headers. Should be called
1456             only once for a single email message.
1457              
1458             =cut
1459              
1460             sub output_headers {
1461 2543     2543 1 6768 my ($self, $out) = @_;
1462 2543 50       11084 $self->_dbpnt( "Outputting headers") if $self->{verbose} > 2;
1463              
1464             # Q-Encoding (RFC 2047)
1465 293     293   5089 my $subj = PERL58 && $self->{subject} =~ /(?:\P{ASCII}|=)/s
  293         336  
  293         3606  
1466             ? Encode::encode( 'MIME-Q', $self->{subject} )
1467 2543 100       61272 : $self->{subject};
1468              
1469             # Q-Encode the phrase part of recipient headers.
1470 2543         1337455 my $norm;
1471 2543         4044 if (PERL58) {
1472 2543         238801 require Email::Address;
1473             $norm = sub {
1474             return join ', ' => map {
1475 5150     5150   10167 my ($addr) = Email::Address->parse($_);
  5434         63363  
1476 5434 100       453565 if ($addr) {
1477 2932 50       124439 if (my $phrase = $addr->phrase) {
1478 0         0 $addr->phrase(Encode::encode( 'MIME-Q', $phrase ));
1479             }
1480 2932         26119 $addr->format;
1481             } else {
1482 2502         14365 $_;
1483             }
1484             } @_;
1485 2543         1411014 };
1486             } else {
1487 0     0   0 $norm = sub { join ', ' => @_ };
1488             }
1489 2543         12887 my $from = $norm->($self->{from});
1490 2543         7584 my $to = $norm->(@{ $self->{to} });
  2543         12909  
1491              
1492 2543         118139 my @headers = (
1493             "MIME-Version: 1.0\n",
1494             "X-Mailer: SVN::Notify " . $self->VERSION
1495             . ": http://search.cpan.org/dist/SVN-Notify/\n",
1496             "From: $from\n",
1497             "Errors-To: $from\n",
1498             "To: $to\n",
1499             "Subject: $subj\n"
1500             );
1501              
1502             push @headers, 'Reply-To: ' . $norm->($self->{reply_to}) . "\n"
1503 2543 100       13262 if $self->{reply_to};
1504              
1505 2543 100       8832 if (my $heads = $self->{add_headers}) {
1506 1         10 while (my ($k, $v) = each %{ $heads }) {
  3         23  
1507 2 100       19 push @headers, "$k: $_\n" for ref $v ? @{ $v } : $v;
  1         7  
1508             }
1509             }
1510              
1511 2543         7032 print $out @{ $self->run_filters( headers => \@headers ) };
  2543         25481  
1512 2543         21005 return $self;
1513             }
1514              
1515             ##############################################################################
1516              
1517             =head3 output_content_type
1518              
1519             $notifier->output_content_type($file_handle);
1520              
1521             Outputs the content type and transfer encoding headers. These demarcate the
1522             body of the message. If the C parameter was set to true, then a
1523             boundary string will be generated and the Content-Type set to
1524             "multipart/mixed" and stored as the C attribute.
1525              
1526             After that, this method outputs the content type returned by
1527             C, the character set specified by the C attribute,
1528             and a Content-Transfer-Encoding of "8bit". Subclasses can either rely on this
1529             functionality or override this method to provide their own content type
1530             headers.
1531              
1532             =cut
1533              
1534             sub output_content_type {
1535 2662     2662 1 8850 my ($self, $out) = @_;
1536 2662 50       9838 $self->_dbpnt( "Outputting content type") if $self->{verbose} > 2;
1537             # Output the content type.
1538 2662 100       6598 if ($self->{attach_diff}) {
1539             # We need a boundary string.
1540 159   66     3288 $self->{boundary} ||= join '', ('a'..'z', 'A'..'Z', 0..9)[ map { rand 62 } 0..10];
  946         3958  
1541 159         1516 print $out
1542             qq{Content-Type: multipart/mixed; boundary="$self->{boundary}"\n\n};
1543             }
1544              
1545 2662         26673 my $ctype = $self->content_type;
1546 2662 100       9094 print $out "--$self->{boundary}\n" if $self->{attach_diff};
1547             print $out "Content-Type: $ctype; charset=$self->{encoding}\n",
1548 2662 100       23192 ($self->{language} ? "Content-Language: $self->{language}\n" : ()),
1549             "Content-Transfer-Encoding: 8bit\n\n";
1550 2662         8204 return $self;
1551             }
1552              
1553             ##############################################################################
1554              
1555             =head3 start_body
1556              
1557             $notifier->start_body($file_handle);
1558              
1559             This method starts the body of the notification message, which means that it
1560             outputs the contents of the C
attribute, if there are any. Otherwise
1561             it outputs nothing, but see subclasses for other behaviors.
1562              
1563             =cut
1564              
1565             sub start_body {
1566 1585     1585 1 3006 my ($self, $out) = @_;
1567 1585 100       4126 my $start = [ $self->{header} ? ("$self->{header}\n") : () ];
1568 1585         4618 $start = $self->run_filters( start_body => $start );
1569 1585 100 66     17516 print $out @$start, "\n" if $start && @$start;
1570 1585         2733 return $self;
1571             }
1572              
1573             ##############################################################################
1574              
1575             =head3 output_metadata
1576              
1577             $notifier->output_metadata($file_handle);
1578              
1579             This method outputs the metadata of the commit, including the revision number,
1580             author (user), and date of the revision. If the C or
1581             C attributes have been set, then the appropriate URL(s) for the
1582             revision will also be output.
1583              
1584             =cut
1585              
1586             sub output_metadata {
1587 1610     1610 1 2723 my ($self, $out) = @_;
1588 1610         9188 my @lines = ("Revision: $self->{revision}\n");
1589 1610 100       9631 if (my $url = $self->{revision_url}) {
1590 103         1138 push @lines, sprintf " $url\n", $self->{revision};
1591             }
1592              
1593             # Output the Author any any relevant URL.
1594 1610         5257 push @lines, "Author: $self->{user}\n";
1595 1610 100       6232 if (my $url = $self->{author_url}) {
1596 32         576 push @lines, sprintf " $url\n", $self->{user};
1597             }
1598              
1599 1610         5845 push @lines, "Date: $self->{date}\n";
1600              
1601 1610         3273 print $out @{ $self->run_filters( metadata => \@lines ) };
  1610         3345  
1602 1610         3749 return $self;
1603             }
1604              
1605             ##############################################################################
1606              
1607             =head3 output_log_message
1608              
1609             $notifier->output_log_message($file_handle);
1610              
1611             Outputs the commit log message, as well as the label "Log Message".
1612              
1613             =cut
1614              
1615             sub output_log_message {
1616 1585     1585 1 2737 my ($self, $out) = @_;
1617 1585 100       4450 $self->_dbpnt( "Outputting log message") if $self->{verbose} > 1;
1618             my $msg = join "\n", @{
1619 1585         2080 $self->run_filters( log_message => $self->{message} )
1620 1585         4340 };
1621              
1622 1585         12589 print $out "Log Message:\n-----------\n$msg\n";
1623              
1624             # Make Revision links.
1625 1585 100       5447 if (my $url = $self->{revision_url}) {
1626 103 50       4687 if (my @matches = $msg =~ /\b(?:(?:rev(?:ision)?\s*#?\s*|r)(\d+))\b/ig) {
1627 103         181094 print $out "\nRevision Links:\n--------------\n";
1628 103         1525 printf $out " $url\n", $_ for @matches;
1629             }
1630             }
1631              
1632             # Make ticketing system links.
1633 1585 100       20608 if (my $map = $self->ticket_map) {
1634 26         260 my $has_header = 0;
1635             $self->run_ticket_map( sub {
1636 156     156   156 my ($regex, $url) = @_;
1637 156         7540 while ($msg =~ /$regex/ig) {
1638 234 100       416 unless ($has_header) {
1639 26         208 print $out "\nTicket Links:\n------------\n";
1640 26         52 $has_header = 1;
1641             }
1642 234   66     5330 printf $out " $url\n", $2 || $1;
1643             }
1644 26         494 } );
1645             }
1646              
1647 1585         3251 return $self;
1648             }
1649              
1650             ##############################################################################
1651              
1652             =head3 output_file_lists
1653              
1654             $notifier->output_file_lists($file_handle);
1655              
1656             Outputs the lists of modified, added, and deleted files, as well as the list
1657             of files for which properties were changed. The labels used for each group are
1658             pulled in from the C class method.
1659              
1660             =cut
1661              
1662             sub output_file_lists {
1663 1607     1607 1 3342 my ($self, $out) = @_;
1664 1607 50       4739 my $files = $self->{files} or return $self;
1665 1607 100       3561 $self->_dbpnt( "Outputting file lists") if $self->{verbose} > 1;
1666 1607         9291 my $map = $self->file_label_map;
1667             # Create the underlines.
1668 1607         19723 my %dash = ( map { $_ => '-' x length($map->{$_}) } keys %$map );
  6428         33250  
1669              
1670 1607         8412 foreach my $type (qw(U A D _)) {
1671             # Skip it if there's nothing to report.
1672 6428 100       12839 next unless $files->{$type};
1673             $self->_dbpnt( " Outputting $map->{$type} file list")
1674 6059 50       10342 if $self->{verbose} > 2;
1675              
1676             # Identify the action and output each file.
1677 6059         8603 print $out "\n", @{ $self->run_filters(
1678             file_lists => [
1679             "$map->{$type}:\n",
1680             "$dash{$type}\n",
1681 6059         16140 map { " $_\n" } @{ $files->{$type} }
  31287         69871  
  6059         33256  
1682             ]
1683             ) };
1684             }
1685 1607         3486 print $out "\n";
1686 1607         4714 return $self;
1687             }
1688              
1689             ##############################################################################
1690              
1691             =head3 end_body
1692              
1693             $notifier->end_body($file_handle);
1694              
1695             Closes out the body of the email by outputting the contents of the C
1696             attribute, if any, and then a couple of newlines. Designed to be called when
1697             the body of the message is complete, and before any call to
1698             C.
1699              
1700             =cut
1701              
1702             sub end_body {
1703 1576     1576 1 4029 my ($self, $out) = @_;
1704 1576 50       5217 $self->_dbpnt( "Ending body") if $self->{verbose} > 2;
1705 1576 100       5108 my $end = [ $self->{footer} ? ("$self->{footer}\n") : () ];
1706 1576         4101 $end = $self->run_filters( end_body => $end );
1707 1576 100 66     14957 print $out @$end, "\n" if $end && @$end;
1708 1576         3199 return $self;
1709             }
1710              
1711             ##############################################################################
1712              
1713             =head3 output_diff
1714              
1715             $notifier->output_diff($out_file_handle, $diff_file_handle);
1716              
1717             Reads diff data from C<$diff_file_handle> and outputs it to to
1718             C<$out_file_handle>.
1719              
1720             =cut
1721              
1722             sub output_diff {
1723 228     228 1 1329 my $self = shift;
1724 228 50       1954 $self->_dbpnt( "Outputting diff") if $self->{verbose} > 1;
1725 228         3683 $self->_dump_diff(@_);
1726             }
1727              
1728             ##############################################################################
1729              
1730             =head3 output_attached_diff
1731              
1732             $notifier->output_attached_diff($out_file_handle, $diff_file_handle);
1733              
1734             Reads diff data from C<$diff_file_handle> and outputs it to to
1735             C<$out_file_handle> as an attachment.
1736              
1737             =cut
1738              
1739             sub output_attached_diff {
1740 155     155 1 824 my ($self, $out, $diff) = @_;
1741 155 50       2366 $self->_dbpnt( "Attaching diff") if $self->{verbose} > 2;
1742             print $out "\n--$self->{boundary}\n",
1743             "Content-Disposition: attachment; filename=",
1744             "r$self->{revision}-$self->{user}.diff\n",
1745             "Content-Type: $self->{diff_content_type}; charset=$self->{encoding}\n",
1746 155 100       4539 ($self->{language} ? "Content-Language: $self->{language}\n" : ()),
1747             "Content-Transfer-Encoding: 8bit\n\n";
1748 155         2279 $self->_dump_diff($out, $diff);
1749             }
1750              
1751             ##############################################################################
1752              
1753             =head3 end_message
1754              
1755             $notifier->end_message($file_handle);
1756              
1757             Outputs the final part of the message,. In this case, that means only a
1758             boundary if the C parameter is true. Designed to be called after
1759             any call to C.
1760              
1761             =cut
1762              
1763             sub end_message {
1764 2633     2633 1 5079 my ($self, $out) = @_;
1765 2633 100       11038 print $out "--$self->{boundary}--\n" if $self->{attach_diff};
1766 2633         3561 return $self;
1767             }
1768              
1769             ##############################################################################
1770              
1771             =head3 run_ticket_map
1772              
1773             $notifier->run_ticket_map( \&callback, @params );
1774              
1775             Loops over the ticket systems you have defined, calling the C<$callback>
1776             function for each one, passing to it the regex, url and @params specified as
1777             its parameters.
1778              
1779             =cut
1780              
1781             sub run_ticket_map {
1782 65     65 1 229 my ($self, $callback, @params) = @_;
1783              
1784             # Make ticketing system links.
1785 65 50       403 my $map = $self->ticket_map or return;
1786 65         275 my $has_header = 0;
1787 65         686 while (my ($regex, $url) = each %$map) {
1788 332   66     1837 $regex = $_ticket_regexen{ $regex } || $regex;
1789 332         769 $callback->( $regex, $url, @params );
1790             }
1791             }
1792              
1793             ##############################################################################
1794              
1795             =head3 run_filters
1796              
1797             $data = $notifier->run_filters( $output_type => $data );
1798              
1799             Runs the filters for C<$output_type> on $data. Used internally by SVN::Notify
1800             and by subclasses.
1801              
1802             =cut
1803              
1804             sub run_filters {
1805 38181     38181 1 116582 my ($self, $type, $data) = @_;
1806 38181 100       323591 my $filters = $self->{filters}{$type} or return $data;
1807 1329         11917 $data = $_->($self, $data) for @$filters;
1808 1329         1612259 return $data;
1809             }
1810              
1811             ##############################################################################
1812              
1813             =head3 filters_for
1814              
1815             my $filters = $notifier->filters_for( $output_type );
1816              
1817             Returns an array reference of of the filters loaded for C<$output_type>.
1818             Returns C if there are no filters have been loaded for C<$output_type>.
1819              
1820             =cut
1821              
1822             sub filters_for {
1823 3579     3579 1 13826 shift->{filters}{+shift};
1824             }
1825              
1826             ##############################################################################
1827              
1828             =head3 diff_handle
1829              
1830             my $diff = $notifier->diff_handle;
1831             while (<$diff>) { print }
1832              
1833             Returns a file handle reference providing access to the the commit diff. It
1834             will usually be passed as the second argument to C or
1835             C.
1836              
1837             =cut
1838              
1839             sub diff_handle {
1840 704     704 1 1986 my $self = shift;
1841             # To avoid svnlook output except for diff contents, such as "Modified"
1842             # etc., to be output in the localized string encoded with another encoding
1843             # from diff contents. HTML and HTML::ColorDiff also expect the terms
1844             # printed in English.
1845 704         9492 local $ENV{LANG} = 'C';
1846              
1847             return $self->_pipe(
1848             $self->{diff_encoding},
1849             '-|' => $self->{svnlook},
1850             'diff' => $self->{repos_path},
1851             '-r' => $self->{revision},
1852             ( $self->{diff_switches}
1853 48 50       996 ? grep { defined && $_ ne '' }
1854             # Allow quoting of arguments, but strip out the quotes.
1855             split /(?:'([^']+)'|"([^"]+)")?\s+(?:'([^']+)'|"([^"]+)")?/,
1856             $self->{diff_switches}
1857             : ()
1858 704 100       7108 ),
1859             );
1860             }
1861              
1862             ##############################################################################
1863             # This method actually dumps the output of C. It's a separate
1864             # method because output_attached_diff() and output_diff() do essentially the
1865             # same thing, so they can both call it. The diff output will be truncated at
1866             # max_diff_length, if specified.
1867             ##############################################################################
1868              
1869             sub _dump_diff {
1870 383     383   1988 my ($self, $out, $diff) = @_;
1871 383         3001 $diff = $self->run_filters( diff => $diff );
1872              
1873 383 100       3115 if (my $max = $self->{max_diff_length}) {
1874 12         168 my $length = 0;
1875 12         7222156 while (<$diff>) {
1876 288         1629 s/[\n\r]+$//;
1877 288 100       527 if (($length += length) < $max) {
1878 276         1188 print $out $_, "\n";
1879             }
1880             else {
1881 12         100 print $out
1882             "\n\@\@ Diff output truncated at $max characters. \@\@\n";
1883 12         68 last;
1884             }
1885             }
1886             }
1887              
1888             else {
1889 371         192100564 while (<$diff>) {
1890 12765         46148128 s/[\n\r]+$//;
1891 12765         119921 print $out $_, "\n";
1892             }
1893             }
1894 383 50       23138 close $diff or warn "Child process exited: $?\n";
1895 383         9385 return $self;
1896             }
1897              
1898             ##############################################################################
1899              
1900             __PACKAGE__->_accessors(qw(
1901             repos_path
1902             revision
1903             to_regex_map
1904             to_email_map
1905             from
1906             user_domain
1907             svnlook
1908             sendmail
1909             set_sender
1910             add_headers
1911             smtp
1912             encoding
1913             diff_encoding
1914             svn_encoding
1915             env_lang
1916             svn_env_lang
1917             language
1918             with_diff
1919             attach_diff
1920             diff_switches
1921             reply_to
1922             subject_prefix
1923             subject_cx
1924             max_sub_length
1925             max_diff_length
1926             author_url
1927             revision_url
1928             ticket_url
1929             ticket_regex
1930             ticket_map
1931             header
1932             footer
1933             verbose
1934             boundary
1935             user
1936             date
1937             message
1938             message_size
1939             subject
1940             files
1941             ));
1942              
1943             ##############################################################################
1944             # This method is used to create accessors for the list of attributes passed to
1945             # it. It creates them both for SVN::Notify (just above) and for all subclasses
1946             # in register_attributes().
1947             ##############################################################################
1948              
1949             sub _accessors {
1950 562     562   902 my $class = shift;
1951 562         1206 for my $attr (@_) {
1952 293     293   4330488 no strict 'refs';
  293         506  
  293         33367  
1953 12385         30434 *{"$class\::$attr"} = sub {
1954 32782     32782   37926 my $self = shift;
1955 32782 100       181672 return $self->{$attr} unless @_;
1956 53         405 $self->{$attr} = shift;
1957 53         132 return $self;
1958 12385         17062 };
1959             }
1960             }
1961              
1962             # Aliases for deprecated attributes.
1963 84     84 1 252 sub svnweb_url { shift->revision_url(@_) }
1964 84     84 1 1092 sub viewcvs_url { shift->revision_url(@_) }
1965 0     0 1 0 sub charset { shift->encoding(@_) }
1966              
1967             # Deprecated ticket URL systems.
1968             for my $tick (qw(rt bugzilla jira gnats)) {
1969 293     293   1037 no strict 'refs';
  293         347  
  293         25829  
1970             *{$tick . '_url'} = sub {
1971 156     156   312 my $self = shift;
1972 156   50     3224 my $map = $self->{ticket_map} || {};
1973 156 50       1040 return $map->{$tick} unless @_;
1974 0 0       0 if (my $url = shift) {
1975 0         0 $map->{$tick} = $url;
1976             } else {
1977 0         0 delete $map->{$tick};
1978             }
1979 0         0 $self->{ticket_map} = $map;
1980 0         0 return $self;
1981             };
1982             }
1983              
1984             for my $attr (qw(to strip_cx_regex)) {
1985 293     293   1171 no strict 'refs';
  293         405  
  293         219710  
1986             *{__PACKAGE__ . "::$attr"} = sub {
1987 117     117   332 my $self = shift;
1988 117 50       734 return wantarray ? @{ $self->{$attr} } : $self->{$attr}[0] unless @_;
  117 50       1144  
1989 0         0 $self->{$attr}= \@_;
1990 0         0 return $self;
1991             };
1992             }
1993              
1994             =head2 Accessors
1995              
1996             =head3 repos_path
1997              
1998             my $repos_path = $notifier->repos_path;
1999             $notifier = $notifier->repos_path($repos_path);
2000              
2001             Gets or sets the value of the C attribute.
2002              
2003             =head3 revision
2004              
2005             my $revision = $notifier->revision;
2006             $notifier = $notifier->revision($revision);
2007              
2008             Gets or sets the value of the C attribute.
2009              
2010             =head3 to
2011              
2012             my $to = $notifier->to;
2013             $notifier = $notifier->to($to);
2014             my @tos = $notifier->to;
2015             $notifier = $notifier->to(@tos);
2016              
2017             Gets or sets the list of values stored in the C attribute. In a scalar
2018             context, it returns only the first value in the list, for backwards
2019             compatibility with older versions of SVN::Notify. In list context, it of
2020             course returns the entire list. Pass in one or more values to set all of the
2021             values for the C attribute.
2022              
2023             =head3 to_regex_map
2024              
2025             my $to_regex_map = $notifier->to_regex_map;
2026             $notifier = $notifier->to_regex_map($to_regex_map);
2027              
2028             Gets or sets the value of the C attribute, which is a hash
2029             reference of email addresses mapped to regular expressions.
2030              
2031             =head3 to_email_map
2032              
2033             my $to_email_map = $notifier->to_email_map;
2034             $notifier = $notifier->to_email_map($to_email_map);
2035              
2036             Gets or sets the value of the C attribute, which is a hash
2037             reference of regular expressions mapped to email addresses.
2038              
2039             =head3 from
2040              
2041             my $from = $notifier->from;
2042             $notifier = $notifier->from($from);
2043              
2044             Gets or sets the value of the C attribute.
2045              
2046             =head3 user_domain
2047              
2048             my $user_domain = $notifier->user_domain;
2049             $notifier = $notifier->user_domain($user_domain);
2050              
2051             Gets or sets the value of the C attribute.
2052              
2053             =head3 svnlook
2054              
2055             my $svnlook = $notifier->svnlook;
2056             $notifier = $notifier->svnlook($svnlook);
2057              
2058             Gets or sets the value of the C attribute.
2059              
2060             =head3 sendmail
2061              
2062             my $sendmail = $notifier->sendmail;
2063             $notifier = $notifier->sendmail($sendmail);
2064              
2065             Gets or sets the value of the C attribute.
2066              
2067             =head3 set_sender
2068              
2069             my $set_sender = $notifier->set_sender;
2070             $notifier = $notifier->set_sender($set_sender);
2071              
2072             Gets or sets the value of the C attribute.
2073              
2074             =head3 smtp
2075              
2076             my $smtp = $notifier->smtp;
2077             $notifier = $notifier->smtp($smtp);
2078              
2079             Gets or sets the value of the C attribute.
2080              
2081             =head3 encoding
2082              
2083             my $encoding = $notifier->encoding;
2084             $notifier = $notifier->encoding($encoding);
2085              
2086             Gets or sets the value of the C attribute. C is an alias
2087             preserved for backward compatibility.
2088              
2089             =head3 svn_encoding
2090              
2091             my $svn_encoding = $notifier->svn_encoding;
2092             $notifier = $notifier->svn_encoding($svn_encoding);
2093              
2094             Gets or sets the value of the C attribute.
2095              
2096             =head3 diff_encoding
2097              
2098             my $diff_encoding = $notifier->diff_encoding;
2099             $notifier = $notifier->diff_encoding($diff_encoding);
2100              
2101             Gets or sets the value of the C attribute.
2102              
2103             =head3 language
2104              
2105             my $language = $notifier->language;
2106             $notifier = $notifier->language($language);
2107              
2108             Gets or sets the value of the C attribute.
2109              
2110             =head3 env_lang
2111              
2112             my $env_lang = $notifier->env_lang;
2113             $notifier = $notifier->env_lang($env_lang);
2114              
2115             Gets or sets the value of the C attribute, which is set to C<<
2116             $notify->language . '.' . $notify->encoding >> when C is set, and
2117             otherwise is C. This attribute is used to set the C<$LANG> environment
2118             variable, if it is not already set by the environment, before executing
2119             C.
2120              
2121             =head3 svn_env_lang
2122              
2123             my $svn_env_lang = $notifier->svn_env_lang;
2124             $notifier = $notifier->svn_env_lang($svn_env_lang);
2125              
2126             Gets or sets the value of the C attribute, which is set to C<<
2127             $notify->language . '.' . $notify->svn_encoding >> when C is set,
2128             and otherwise is C. This attribute is used to set the C<$LANG>
2129             environment variable, if it is not already set by the environment, before
2130             executing C. It is not used for C, however, as the diff
2131             itself will be emitted in raw octets except for headers such as "Modified",
2132             which need to be in English so that subclasses can parse them. Thus, C<$LANG>
2133             is always set to "C" for the execution of C.
2134              
2135             =head3 with_diff
2136              
2137             my $with_diff = $notifier->with_diff;
2138             $notifier = $notifier->with_diff($with_diff);
2139              
2140             Gets or sets the value of the C attribute.
2141              
2142             =head3 attach_diff
2143              
2144             my $attach_diff = $notifier->attach_diff;
2145             $notifier = $notifier->attach_diff($attach_diff);
2146              
2147             Gets or sets the value of the C attribute.
2148              
2149             =head3 diff_switches
2150              
2151             my $diff_switches = $notifier->diff_switches;
2152             $notifier = $notifier->diff_switches($diff_switches);
2153              
2154             Gets or sets the value of the C attribute.
2155              
2156             =head3 reply_to
2157              
2158             my $reply_to = $notifier->reply_to;
2159             $notifier = $notifier->reply_to($reply_to);
2160              
2161             Gets or sets the value of the C attribute.
2162              
2163             =head3 add_headers
2164              
2165             my $add_headers = $notifier->add_headers;
2166             $notifier = $notifier->add_headers({
2167             'X-Accept' => [qw(This That)],
2168             'X-Reject' => 'Me!',
2169             });
2170              
2171             Gets or sets the value of the C attribute, which is a hash
2172             reference of the headers to be added to the email message. If one header needs
2173             to appear multiple times, simply pass the corresponding hash value as an array
2174             reference of each value for the header. Not to be confused with the C
2175             accessor, which gets and sets text to be included at the beginning of the body
2176             of the email message.
2177              
2178             =head3 subject_prefix
2179              
2180             my $subject_prefix = $notifier->subject_prefix;
2181             $notifier = $notifier->subject_prefix($subject_prefix);
2182              
2183             Gets or sets the value of the C attribute.
2184              
2185             =head3 subject_cx
2186              
2187             my $subject_cx = $notifier->subject_cx;
2188             $notifier = $notifier->subject_cx($subject_cx);
2189              
2190             Gets or sets the value of the C attribute.
2191              
2192             =head3 strip_cx_regex
2193              
2194             my $strip_cx_regex = $notifier->strip_cx_regex;
2195             $notifier = $notifier->strip_cx_regex($strip_cx_regex);
2196             my @strip_cx_regexs = $notifier->strip_cx_regex;
2197             $notifier = $notifier->strip_cx_regex(@strip_cx_regexs);
2198              
2199             Gets or sets the list of values stored in the C attribute. In
2200             a scalar context, it returns only the first value in the list; in list
2201             context, it of course returns the entire list. Pass in one or more values to
2202             set all of the values for the C attribute.
2203              
2204             =head3 max_sub_length
2205              
2206             my $max_sub_length = $notifier->max_sub_length;
2207             $notifier = $notifier->max_sub_length($max_sub_length);
2208              
2209             Gets or sets the value of the C attribute.
2210              
2211             =head3 max_diff_length
2212              
2213             my $max_diff_length = $notifier->max_diff_length;
2214             $notifier = $notifier->max_diff_length($max_diff_length);
2215              
2216             Gets or set the value of the C attribute.
2217              
2218             =head3 author_url
2219              
2220             my $author_url = $notifier->author_url;
2221             $notifier = $notifier->author_url($author_url);
2222              
2223             Gets or sets the value of the C attribute.
2224              
2225             =head3 revision_url
2226              
2227             my $revision_url = $notifier->revision_url;
2228             $notifier = $notifier->revision_url($revision_url);
2229              
2230             Gets or sets the value of the C attribute.
2231              
2232             =head3 svnweb_url
2233              
2234             Deprecated. Pleas use C, instead.
2235              
2236             =head3 viewcvs_url
2237              
2238             Deprecated. Pleas use C, instead.
2239              
2240             =head3 verbose
2241              
2242             my $verbose = $notifier->verbose;
2243             $notifier = $notifier->verbose($verbose);
2244              
2245             Gets or sets the value of the C attribute.
2246              
2247             =head3 boundary
2248              
2249             my $boundary = $notifier->boundary;
2250             $notifier = $notifier->boundary($boundary);
2251              
2252             Gets or sets the value of the C attribute. This string is normally
2253             set by a call to C, but may be set ahead of time.
2254              
2255             =head3 user
2256              
2257             my $user = $notifier->user;
2258             $notifier = $notifier->user($user);
2259              
2260             Gets or sets the value of the C attribute, which is set to the value
2261             pulled in from F by the call to C.
2262              
2263             =head3 date
2264              
2265             my $date = $notifier->date;
2266             $notifier = $notifier->date($date);
2267              
2268             Gets or sets the value of the C attribute, which is set to the value
2269             pulled in from F by the call to C.
2270              
2271             =head3 message
2272              
2273             my $message = $notifier->message;
2274             $notifier = $notifier->message($message);
2275              
2276             Gets or sets the value of the C attribute, which is set to an array
2277             reference of strings by the call to C.
2278              
2279             =head3 message_size
2280              
2281             my $message_size = $notifier->message_size;
2282             $notifier = $notifier->message_size($message_size);
2283              
2284             Gets or sets the value of the C attribute, which is set to the
2285             value pulled in from F by the call to C.
2286              
2287             =head3 subject
2288              
2289             my $subject = $notifier->subject;
2290             $notifier = $notifier->subject($subject);
2291              
2292             Gets or sets the value of the C attribute, which is normally set
2293             by a call to C, but may be set explicitly.
2294              
2295             =head3 files
2296              
2297             my $files = $notifier->files;
2298             $notifier = $notifier->files($files);
2299              
2300             Gets or sets the value of the C attribute, which is set to a hash
2301             reference of change type mapped to arrays of strings by the call to
2302             C.
2303              
2304             =head3 header
2305              
2306             my $header = $notifier->header;
2307             $notifier = $notifier->header($header);
2308              
2309             Gets or set the value of the C
attribute. Not to be confused with the
2310             C attribute, which manages headers to be inserted into the
2311             notification email message headers.
2312              
2313             =head3 footer
2314              
2315             my $footer = $notifier->footer;
2316             $notifier = $notifier->footer($footer);
2317              
2318             Gets or set the value of the C
attribute.
2319              
2320             =cut
2321              
2322             ##############################################################################
2323             # This method forks off a process to execute an external program and any
2324             # associated arguments and returns a file handle that can be read from to
2325             # fetch the output of the external program, or written to. Pass "-|" as the
2326             # sole argument to read from another process (such as svnlook), and pass "|-"
2327             # to write to another process (such as sendmail).
2328             ##############################################################################
2329              
2330             sub _pipe {
2331 9133     9133   25607 my ($self, $encode, $mode) = (shift, shift, shift);
2332             $self->_dbpnt( q{Piping execution of "} . join(q{" "}, @_) . q{"})
2333 9133 100       27855 if $self->{verbose};
2334             # Safer version of backtick (see perlipc(1)).
2335 9133         29450 local *PIPE;
2336 9133         12083 if (WIN32) {
2337             my $cmd = $mode eq '-|'
2338             ? q{"} . join(q{" "}, @_) . q{"|}
2339             : q{|"} . join(q{" "}, @_) . q{"};
2340             open PIPE, $cmd or die "Cannot fork: $!\n";
2341             binmode PIPE, ":encoding($encode)" if PERL58 && $encode;
2342             return *PIPE;
2343             }
2344              
2345 9133         6165898 my $pid = open PIPE, $mode;
2346 9133 50       144534 die "Cannot fork: $!\n" unless defined $pid;
2347              
2348 9133 100       132627 if ($pid) {
2349             # Parent process. Set the encoding layer and return the file handle.
2350 8848 50   285   668477 binmode PIPE, ":encoding($encode)" if PERL58 && $encode;
  285         5202  
  285         1299  
  285         7183  
2351 8848         2168657 return *PIPE;
2352             } else {
2353             # Child process. Execute the commands.
2354 285 0       0 exec @_ or die "Cannot exec $_[0]: $!\n";
2355             # Not reached.
2356             }
2357             }
2358              
2359             ##############################################################################
2360             # This method passes its arguments to _pipe(), but then fetches each line
2361             # off output from the returned file handle, safely strips out and replaces any
2362             # newlines and carriage returns, and returns an array reference of those
2363             # lines.
2364             ##############################################################################
2365              
2366             sub _read_pipe {
2367 2793     2793   4178 my $self = shift;
2368 2793         12927 my $fh = $self->_pipe( $self->{svn_encoding}, '-|', @_ );
2369 2709         34642 local $/; my @lines = split /(?:\r\n|\r|\n)/, <$fh>;
  2709         2113831872  
2370 2709 50       1102354 close $fh or warn "Child process exited: $?\n";
2371 2709         39246 return \@lines;
2372             }
2373              
2374             ##############################################################################
2375             # This method is used for debugging output in various verbose modes.
2376             ##############################################################################
2377              
2378 0     0   0 sub _dbpnt { print ref(shift), ': ', join ' ', @_; }
2379              
2380             ##############################################################################
2381             # This function is used to exit the program with an error if a parameter is
2382             # missing.
2383             ##############################################################################
2384              
2385             sub _usage {
2386 3     3   5 my ($msg) = @_;
2387              
2388             # Just die if the API is used.
2389 3 50       29 die $msg if $0 !~ /\bsvnnotify(?:[.]bat)?$/;
2390              
2391             # Otherwise, tell 'em how to use it.
2392 0         0 $msg =~ s/_/-/g;
2393 0         0 $msg =~ s/(\s+")/$1--/g;
2394 0         0 $msg =~ s/\bparameter\b/option/g;
2395 0         0 require Pod::Usage;
2396 0         0 Pod::Usage::pod2usage(
2397             '-message' => $msg,
2398             '-verbose' => 99,
2399             '-sections' => '(?i:(Usage|Options))',
2400             '-exitval' => 1,
2401             );
2402             }
2403              
2404             package SVN::Notify::SMTP;
2405              
2406             $SVN::Notify::SMTP::VERSION = '2.86';
2407              
2408             sub get_handle {
2409 4     4   10 my ($class, $notifier) = @_;
2410              
2411             # Load Net::SMTP::TLS.
2412 4         54 require Net::SMTP::TLS;
2413 4         1847 require Sys::Hostname;
2414             my $smtp = Net::SMTP::TLS->new(
2415             $notifier->{smtp},
2416             Hello => Sys::Hostname::hostname(),
2417             ( $notifier->{smtp_port} ? ( Port => $notifier->{smtp_port} ) : () ),
2418             ( $notifier->{smtp_tls} ? () : (NoTLS => 1) ),
2419             ( $notifier->{smtp_user} ? ( User => $notifier->{smtp_user} ) : () ),
2420             ( $notifier->{smtp_pass} ? ( Password => $notifier->{smtp_pass} ) : () ),
2421 4 100       3948 ( $notifier->{verbose} ? ( Debug => 1 ) : () )
    50          
    100          
    100          
    100          
    50          
2422             ) or die "Unable to create SMTP object: $!";
2423              
2424 4         172 $smtp->mail($notifier->{from});
2425 4         19 $smtp->to(map { split /\s*,\s*/ } @{ $notifier->{to} });
  8         34  
  4         12  
2426 4         30 $smtp->data;
2427 4         42 tie local(*SMTP), $class, $smtp, $notifier;
2428             # Perl 5.6 requires the escape.
2429 4         21 return SVN::Notify::PERL58 ? *SMTP : \*SMTP;
2430             }
2431              
2432             sub TIEHANDLE {
2433 4     4   8 my ($class, $smtp, $notifier) = @_;
2434 4         21 bless { smtp => $smtp, notifier => $notifier }, $class;
2435             }
2436              
2437             sub PRINT {
2438 44     44   38 my $self = shift;
2439 44         32 if (SVN::Notify::PERL58) {
2440 44         77 my $encode = $self->{notifier}->encoding;
2441             return $self->{smtp}->datasend( map {
2442 44         66 Encode::encode( $encode, $_ )
  196         2852  
2443             } @_ )
2444             }
2445 0         0 return $self->{smtp}->datasend(@_);
2446             }
2447              
2448             sub PRINTF {
2449 4     4   8 my $self = shift;
2450 4         23 $self->PRINT( sprintf(shift, @_) );
2451             }
2452              
2453             sub CLOSE {
2454 4     4   3 my $self = shift;
2455 4         12 $self->{smtp}->dataend;
2456 4         15 $self->{smtp}->quit;
2457             }
2458              
2459             1;
2460             __END__