File Coverage

blib/lib/SVN/Notify.pm
Criterion Covered Total %
statement 450 476 94.5
branch 223 284 78.5
condition 77 112 68.7
subroutine 52 54 96.3
pod 30 30 100.0
total 832 956 87.0


line stmt bran cond sub pod time code
1             package SVN::Notify;
2              
3 293     293   409453 use strict;
  293         885  
  293         24713  
4             require 5.006_000;
5 293     293   42613 use constant WIN32 => $^O eq 'MSWin32';
  293         683  
  293         50870  
6 293     293   1915 use constant PERL58 => $] > 5.007_000;
  293         656  
  293         246646  
7             require Encode if PERL58;
8             $SVN::Notify::VERSION = '2.84';
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 55835994 my ($class, %params) = @_;
685              
686             # Delegate to a subclass if requested.
687 3406 100       29004 if (my $handler = delete $params{handler}) {
688 346         1302 my $subclass = __PACKAGE__ . "::$handler";
689 346 50       1685 unless ($subclass eq $class) {
690 346 50       35089 eval "require $subclass" or die $@;
691 346         6913 return $subclass->new(%params);
692             }
693             }
694              
695             # Load any filters.
696 3060   100     57155 $params{filters} ||= {};
697 3060 100       31378 if (ref $params{filters} eq 'ARRAY') {
698 716         1740 my $filts = {};
699 716         1182 for my $pkg ( @{ $params{filters} } ) {
  716         2213  
700 778 100       5751 $pkg = "SVN::Notify::Filter::$pkg" if $pkg !~ /::/;
701 778 100       4241 if ($filters{$pkg}) {
702 308         645 while (my ($k, $v) = each %{ $filters{$pkg} }) {
  876         6088  
703 568   50     4457 $filts->{$k} ||= [];
704 568         989 push @{ $filts->{$k} }, $v;
  568         1813  
705             }
706             } else {
707 470 50       50823 eval "require $pkg" or die $@;
708 470         3425 $filters{$pkg} = {};
709 293     293   2007 no strict 'refs';
  293         702  
  293         3292129  
710 470         1163 while ( my ($k, $v) = each %{ "$pkg\::" } ) {
  1355         16128  
711 885 100       1789 my $code = *{$v}{CODE} or next;
  885         6251  
712 769         2604 $filters{$pkg}->{$k} = $code;
713 769   100     14341 $filts->{$k} ||= [];
714 769         2988 push @{ $filts->{$k} }, $code;
  769         2628  
715             }
716             }
717             }
718 716         2068 $params{filters} = $filts;
719             }
720              
721             # Make sure that the tos are an arrayref.
722 3060 100 66     32942 $params{to} = [ $params{to} || () ] unless ref $params{to};
723              
724             # Check for required parameters.
725 3060 100       18787 $class->_dbpnt( "Checking required parameters to new()")
726             if $params{verbose};
727 3060 100       12014 _usage( qq{Missing required "repos_path" parameter} )
728             unless $params{repos_path};
729 3059 100       17689 _usage( qq{Missing required "revision" parameter} )
730             unless $params{revision};
731              
732             # Set up default values.
733 3058   0     20878 $params{svnlook} ||= $ENV{SVNLOOK} || $class->find_exe('svnlook');
      33        
734 3058   100     40671 $params{with_diff} ||= $params{attach_diff};
735 3058   100     37113 $params{verbose} ||= 0;
736 3058   50     37711 $params{encoding} ||= $params{charset} || 'UTF-8';
      66        
737 3058   66     25409 $params{svn_encoding} ||= $params{encoding};
738 3058   66     29026 $params{diff_encoding} ||= $params{svn_encoding};
739 3058   50     111697 $params{diff_content_type} ||= $params{diff_content_type} || 'text/plain';
      66        
740 3058 100 0     16988 $params{sendmail} ||= $ENV{SENDMAIL} || $class->find_exe('sendmail')
      33        
741             unless $params{smtp};
742              
743 3058 50 66     11413 _usage( qq{Cannot find sendmail and no "smtp" parameter specified} )
744             unless $params{sendmail} || $params{smtp};
745              
746             # Set up the environment locale.
747 3058 100 66     14663 if ( $params{language} && !$ENV{LANG} ) {
748 210         1474 ( my $lang_country = $params{language} ) =~ s/-/_/g;
749 210         940 for my $p (qw(encoding svn_encoding)) {
750 420         3880 my $encoding = $params{$p};
751 420 50       2138 $encoding =~ s/-//g if uc($encoding) ne 'UTF-8';
752 420         7236 (my $label = $p ) =~ s/(_?)encoding/$1/;
753 420         2681 $params{"${label}env_lang"} = "$lang_country.$encoding";
754             }
755             }
756              
757             # Set up the revision URL.
758 3058   100     108427 $params{revision_url} ||= delete $params{svnweb_url}
      100        
759             || delete $params{viewcvs_url};
760 3058 50 66     18253 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         7970 my $track = $params{ticket_map};
767 3058 100       9123 if ($params{ticket_regex}) {
768 77         555 $track->{ delete $params{ticket_regex} } = delete $params{ticket_url};
769             }
770              
771 3058         9357 for my $system (qw(rt bugzilla jira gnats)) {
772 12232         25403 my $param = $system . '_url';
773 12232 100       60209 if ($params{ $param }) {
774 283         1320 $track->{ $system } = delete $params{ $param };
775 283 50       2310 warn "--$system-url must have '%s' format\n"
776             unless $track->{ $system } =~ /%s/;
777             }
778             }
779 3058 100       9281 $params{ticket_map} = $track if $track;
780              
781             # Make it so!
782 3058 100       10973 $class->_dbpnt( "Instantiating $class object") if $params{verbose};
783 3058         39960 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 16076 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 1033 my $class = shift;
827 269         707 my @attrs;
828 269         1732 while (@_) {
829 665         4304 push @attrs, shift;
830 665 50       4638 if (my $opt = shift) {
831 665         3064 $OPTS{$attrs[-1]} = $opt;
832             }
833             }
834 269         2847 $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 281554 my $class = shift;
862 4         15 my $opts = {};
863 4         970140 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         32855 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   49471 shift; push @{ $opts->{add_headers}{+shift} }, shift
  6         12  
  6         46  
916             },
917 4 50       2480 '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             ) or return;
921              
922             # Load a subclass if one has been specified.
923 4 100       5427 if (my $hand = $opts->{handler}) {
924 1 50       149 eval "require " . __PACKAGE__ . "::$hand" or die $@;
925 1 50       9 if ($hand eq 'Alternative') {
926             # Load the alternative subclasses.
927 0         0 Getopt::Long::GetOptions(
928 0         0 map { delete $OPTS{$_} => \$opts->{$_} } keys %OPTS
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       23 if ($opts->{filters}) {
938 1         2 for my $pkg ( @{ $opts->{filters} } ) {
  1         6  
939 1 50       6 $pkg = "SVN::Notify::Filter::$pkg" if $pkg !~ /::/;
940 1 50       90 eval "require $pkg" or die $@;
941             }
942             }
943              
944             # Disallow pass-through so that any invalid options will now fail.
945 4         19 Getopt::Long::Configure (qw(no_pass_through));
946 4         198 my @to_decode;
947 4 100       18 if (%OPTS) {
948             # Get a list of string options we'll need to decode.
949 2         10 @to_decode = map { $OPTS{$_} } grep { /=s$/ } keys %OPTS
  0         0  
  4         13  
950             if PERL58;
951              
952             # Load any other options.
953 4         21 Getopt::Long::GetOptions(
954 2         7 map { delete $OPTS{$_} => \$opts->{$_} } keys %OPTS
955             );
956             } else {
957             # Call GetOptions() again so that invalid options will be properly
958             # caught.
959 2         9 Getopt::Long::GetOptions();
960             }
961              
962 4         1168 if (PERL58) {
963             # Decode all string options.
964 4   50     61 my $encoding = $opts->{encoding} || 'UTF-8';
965 4         16 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 84 100       7469 $opts->{$opt} = Encode::decode( $encoding, $opts->{$opt} )
989             if $opts->{$opt};
990             }
991             }
992              
993             # Clear the extra options specifications and return.
994 4         40 %OPTS = ();
995 4         34 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 20896 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 5 my ($class, $exe) = @_;
1036 1         9 $exe .= '.exe' if WIN32;
1037 1         19 require File::Spec;
1038 1         9 require Config;
1039 1         22 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         78 my $file = File::Spec->catfile($path, $exe);
1047 3 100 66     89 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 14866 my $self = shift;
1080 2714         19919 $self->run_filters('pre_prepare');
1081 2714         15460 _usage(
1082             qq{Missing required "to", "to_regex_map", or "to_email_map" parameter}
1083 2714 50 66     4678 ) unless @{$self->{to}} || $self->{to_regex_map} || $self->{to_email_map};
      66        
1084 2713         20591 $self->prepare_recipients;
1085 2707 50       4575 return $self unless @{ $self->{to} };
  2707         16577  
1086 2707         16935 $self->prepare_contents;
1087 2624         39115 $self->prepare_files;
1088 2541         45926 $self->prepare_subject;
1089 2541         11012 $self->run_filters('post_prepare');
1090 2541         93667 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 5988 my $self = shift;
1114 2799 100       10541 $self->_dbpnt( "Preparing recipients list") if $self->{verbose};
1115 2799 100 100     47706 unless (
      100        
1116             $self->{to_regex_map}
1117             || $self->{subject_cx}
1118             || $self->{to_email_map}
1119             ) {
1120 2493         9181 $self->{to} = $self->run_filters( recipients => $self->{to} );
1121 2493         45468 return $self;
1122             }
1123              
1124             # Prevent duplication.
1125 306         1101 my $tos = $self->{to} = [ @{ $self->{to} } ];
  306         2126  
1126              
1127 0         0 my $regexen = $self->{to_regex_map} && $self->{to_email_map}
1128 0         0 ? [ %{ $self->{to_regex_map} }, reverse %{ $self->{to_email_map } } ]
  75         600  
1129             : $self->{to_regex_map} ? [ %{ $self->{to_regex_map} } ]
1130 306 100 66     7206 : $self->{to_email_map} ? [ reverse %{ $self->{to_email_map } } ]
  71 100       355  
    50          
1131             : undef;
1132              
1133 306 100       1219 if ($regexen) {
1134 146 50       655 $self->_dbpnt( "Compiling regex_map regular expressions")
1135             if $self->{verbose} > 1;
1136 146         809 for (my $i = 1; $i < @$regexen; $i += 2) {
1137 438 50       1105 $self->_dbpnt( qq{Compiling "$_"}) if $self->{verbose} > 2;
1138             # Remove initial slash and compile.
1139 438         1823 $regexen->[$i] =~ s|^\^[/\\]|^|;
1140 438         8129 $regexen->[$i] = qr/$regexen->[$i]/;
1141             }
1142             } else {
1143 160         648 $regexen = [];
1144             }
1145              
1146 306 50       1303 local $ENV{LANG} = "$self->{svn_env_lang}" if $self->{svn_env_lang};
1147 306         1737 my $fh = $self->_pipe(
1148             $self->{svn_encoding},
1149             '-|', $self->{svnlook},
1150             'changed',
1151             $self->{repos_path},
1152             '-r', $self->{revision},
1153             );
1154              
1155             # Read in a list of the files changed.
1156 300         3968 my ($cx, %seen);
1157 300         617674823 while (<$fh>) {
1158 4993         46677 s/^.\s*//;
1159 4993         77703 s/[\n\r\/\\]+$//;
1160 4993         17178 for (my $i = 0; $i < @$regexen; $i += 2) {
1161 8640         14936 my ($email, $rx) = @{$regexen}[$i, $i + 1];
  8640         13216  
1162             # If the file matches the regex, save the email.
1163 8640 100       58034 if (/$rx/) {
1164 2448 50       5526 $self->_dbpnt( qq{"$_" matched $rx}) if $self->{verbose} > 2;
1165 2448 100       10628 push @$tos, $email unless $seen{$email}++;
1166             }
1167             }
1168             # Grab the context if it's needed for the subject.
1169 4993 100       102957 if ($self->{subject_cx}) {
1170             # XXX Do we need to set utf8 here?
1171 2113         5356 my $l = length;
1172 2113   66     53390 $cx ||= $_;
1173 2113   100     242862 $cx =~ s{[/\\]?[^/\\]+$}{} until !$cx || m{^\Q$cx\E(?:$|/|\\)};
1174             }
1175             }
1176 300 50 66     6649 $self->_dbpnt( qq{Context is "$cx"})
1177             if $self->{subject_cx} && $self->{verbose} > 1;
1178 300 50       26164 close $fh or warn "Child process exited: $?\n";
1179 300         3094 $self->{cx} = $cx;
1180 300         5205 $tos = $self->run_filters( recipients => $tos );
1181 300 50       1430 $self->_dbpnt( 'Recipients: "', join(', ', @$tos), '"')
1182             if $self->{verbose} > 1;
1183 300         7073 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 6226 my $self = shift;
1200 2793 100       9356 $self->_dbpnt( "Preparing contents") if $self->{verbose};
1201 2793 100       36863 local $ENV{LANG} = "$self->{svn_env_lang}" if $self->{svn_env_lang};
1202 2793         22170 my $lines = $self->_read_pipe($self->{svnlook}, 'info', $self->{repos_path},
1203             '-r', $self->{revision});
1204 2709         27896 $self->{user} = shift @$lines;
1205 2709         20950 $self->{date} = shift @$lines;
1206 2709         21068 $self->{message_size} = shift @$lines;
1207 2709         12979 $self->{message} = $lines;
1208              
1209             # Set up the from address.
1210 2709 50       13229 unless ($self->{from}) {
1211 2709 100       57376 $self->{from} = $self->{user}
1212             . ( $self->{user_domain} ? "\@$self->{user_domain}" : '' );
1213             }
1214 2709         234845 $self->{from} = $self->run_filters( from => $self->{from} );
1215              
1216 2709 100       17675 if ($self->{verbose} > 1) {
1217 2         148 $self->_dbpnt( "From: $self->{from}");
1218 2         40 $self->_dbpnt( "Message: @$lines");
1219             }
1220 2709         30084 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 9659 my $self = shift;
1243 2709 100       28021 $self->_dbpnt( "Preparing file lists") if $self->{verbose};
1244 2709         5820 my %files;
1245 2709 100       12759 local $ENV{LANG} = "$self->{svn_env_lang}" if $self->{svn_env_lang};
1246 2709         27996 my $fh = $self->_pipe(
1247             $self->{svn_encoding},
1248             '-|', $self->{svnlook},
1249             'changed',
1250             $self->{repos_path},
1251             '-r', $self->{revision},
1252             );
1253              
1254             # Read in a list of changed files.
1255 2625         8731877530 my $cx = $_ = <$fh>;
1256 2625         152398 do {
1257 46761         991677 s/[\n\r]+$//;
1258 46761 50       567185 if (s/^(.)(.)\s+//) {
1259 46761 50       118886 $self->_dbpnt( "$1,$2 => $_") if $self->{verbose} > 2;
1260 46761         59179 push @{$files{$1}}, $_;
  46761         321105  
1261 46761 100 100     2482699 push @{$files{_}}, $_ if $2 ne ' ' && $1 ne '_';
  2404         25526  
1262             }
1263             } while (<$fh>);
1264              
1265 2625 100 100     24379 if ($self->{subject_cx} && $. == 1) {
1266             # There's only one file; it's the context.
1267 51         1683 $cx =~ s/[\n\r]+$//;
1268 51         1887 ($self->{cx} = $cx) =~ s/^..\s+//;
1269 51 50       765 $self->_dbpnt( qq{File context is "$self->{cx}"})
1270             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       949447 close $fh or warn "Child process exited: $?\n";
1275 2625         34829 $self->{files} = \%files;
1276 2625         58141 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 9539 my $self = shift;
1298 2625 100       13100 $self->_dbpnt( "Preparing subject") if $self->{verbose};
1299              
1300 2625         20356 $self->{subject} = '';
1301              
1302             # Start with the optional message and revision number..
1303 2625 100       13785 if ( defined $self->{subject_prefix} ) {
1304 127 100       12432 if ( index($self->{subject_prefix}, '%d') > 0 ) {
1305 59         3186 $self->{subject} .=
1306             sprintf $self->{subject_prefix}, $self->{revision};
1307             } else {
1308 68         870 $self->{subject} .=
1309             $self->{subject_prefix} . "[$self->{revision}] ";
1310             }
1311             } else {
1312 2498         18751 $self->{subject} .= "[$self->{revision}] ";
1313             }
1314              
1315             # Add the context if there is one.
1316 2625 100       13276 if ($self->{cx}) {
1317 148 100       1439 if (my $rx = $self->{strip_cx_regex}) {
1318 42         4378 $self->{cx} =~ s/$_// for @$rx;
1319             }
1320 148 100       2219 my $space = $self->{no_first_line} ? '' : ': ';
1321 148 50       1226 $self->{subject} .= $self->{cx} . $space if $self->{cx};
1322             }
1323              
1324             # Add the first sentence/line from the log message.
1325 2625 100       10962 unless ($self->{no_first_line}) {
1326             # Truncate to first period after a minimum of 10 characters.
1327 2583         21576 my $min = length $self->{message}[0];
1328 2583 50       13427 $min = 10 if $min > 10;
1329 2583         37977 my $i = index substr($self->{message}[0], $min), '. ';
1330 2583 100       24658 $self->{subject} .= $i > 0
1331             ? substr($self->{message}[0], 0, $i + 11)
1332             : $self->{message}[0];
1333             }
1334              
1335             # Truncate to the last word under 72 characters.
1336 2625 100 66     15687 $self->{subject} =~ s/^(.{0,$self->{max_sub_length}})\s+.*$/$1/m
1337             if $self->{max_sub_length}
1338             && length $self->{subject} > $self->{max_sub_length};
1339              
1340             # Now filter it.
1341 2625         42003 $self->{subject} = $self->run_filters( subject => $self->{subject} );
1342 2625 100       9946 $self->_dbpnt( qq{Subject is "$self->{subject}"}) if $self->{verbose};
1343              
1344 2625         5654 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 13558 my $self = shift;
1362 2625 100       11244 $self->_dbpnt( "Sending message") if $self->{verbose};
1363 2625         9532 $self->run_filters('pre_execute');
1364 2625 50       6018 return $self unless @{ $self->{to} };
  2625         15708  
1365              
1366 2625 100       10710 my $out = $self->{smtp} ? SVN::Notify::SMTP->get_handle($self) : do {
1367 2621 100       10135 local $ENV{LANG} = $self->{env_lang} if $self->{env_lang};
1368 2621 50       24332 $self->_pipe(
1369             $self->{encoding},
1370             '|-', $self->{sendmail},
1371             '-oi', '-t',
1372             ($self->{set_sender} ? ('-f', $self->{from}) : ())
1373             );
1374             };
1375              
1376             # Output the message.
1377 2543         488863 $self->output($out);
1378              
1379 2514 50       6776161588 close $out or warn "Child process exited: $?\n";
1380 2514 100       42711 $self->_dbpnt( 'Message sent' ) if $self->{verbose};
1381 2514         23909 $self->run_filters('post_execute');
1382 2514         201803 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 19901 my ($self, $out, $no_headers) = @_;
1424 2662 100       21156 $self->_dbpnt( "Outputting notification message") if $self->{verbose} > 1;
1425 2662 100       38519 $self->output_headers($out) unless $no_headers;
1426 2662         49209 $self->output_content_type($out);
1427 2662         31381 $self->start_body($out);
1428 2662         24138 $self->output_metadata($out);
1429 2662         20981 $self->output_log_message($out);
1430 2662         39521 $self->output_file_lists($out);
1431 2662 100       12545 if ($self->{with_diff}) {
1432             # Get a handle on the diff output.
1433 704         19337 my $diff = $self->diff_handle;
1434 675 100       59852 if ($self->{attach_diff}) {
1435 155         10937 $self->end_body($out);
1436 155         14353 $self->output_attached_diff($out, $diff);
1437             } else {
1438 520         128282 $self->output_diff($out, $diff);
1439 520         6974 $self->end_body($out);
1440             }
1441             } else {
1442 1958         19640 $self->end_body($out);
1443             }
1444 2633         24999 $self->end_message($out);
1445              
1446 2633         26362 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 26994 my ($self, $out) = @_;
1462 2543 50       14820 $self->_dbpnt( "Outputting headers") if $self->{verbose} > 2;
1463              
1464             # Q-Encoding (RFC 2047)
1465 7629         6692686 my ($subj, $from, $to) = PERL58 ? map {
1466 2543         187857 Encode::encode( 'MIME-Q', $_ );
1467             } $self->{subject}, $self->{from}, join ', ', @{ $self->{to} } : (
1468 2543         12536 $self->{subject}, $self->{from}, join ', ', @{ $self->{to} }
1469             );
1470              
1471 2543         345208 my @headers = (
1472             "MIME-Version: 1.0\n",
1473             "X-Mailer: SVN::Notify " . $self->VERSION
1474             . ": http://search.cpan.org/dist/SVN-Notify/\n",
1475             "From: $from\n",
1476             "Errors-To: $from\n",
1477             "To: $to\n",
1478             "Subject: $subj\n"
1479             );
1480              
1481 2543 100       21943 push @headers, "Reply-To: $self->{reply_to}\n" if $self->{reply_to};
1482              
1483 2543 100       17411 if (my $heads = $self->{add_headers}) {
1484 1         35 while (my ($k, $v) = each %{ $heads }) {
  3         58  
1485 2 100       24 push @headers, "$k: $_\n" for ref $v ? @{ $v } : $v;
  1         21  
1486             }
1487             }
1488              
1489 2543         9151 print $out @{ $self->run_filters( headers => \@headers ) };
  2543         79375  
1490 2543         15130 return $self;
1491             }
1492              
1493             ##############################################################################
1494              
1495             =head3 output_content_type
1496              
1497             $notifier->output_content_type($file_handle);
1498              
1499             Outputs the content type and transfer encoding headers. These demarcate the
1500             body of the message. If the C parameter was set to true, then a
1501             boundary string will be generated and the Content-Type set to
1502             "multipart/mixed" and stored as the C attribute.
1503              
1504             After that, this method outputs the content type returned by
1505             C, the character set specified by the C attribute,
1506             and a Content-Transfer-Encoding of "8bit". Subclasses can either rely on this
1507             functionality or override this method to provide their own content type
1508             headers.
1509              
1510             =cut
1511              
1512             sub output_content_type {
1513 2662     2662 1 9160 my ($self, $out) = @_;
1514 2662 50       20600 $self->_dbpnt( "Outputting content type") if $self->{verbose} > 2;
1515             # Output the content type.
1516 2662 100       9322 if ($self->{attach_diff}) {
1517             # We need a boundary string.
1518 159   66     6984 $self->{boundary} ||= join '', ('a'..'z', 'A'..'Z', 0..9)[ map { rand 62 } 0..10];
  946         10286  
1519 159         2153 print $out
1520             qq{Content-Type: multipart/mixed; boundary="$self->{boundary}"\n\n};
1521             }
1522              
1523 2662         163964 my $ctype = $self->content_type;
1524 2662 100       12571 print $out "--$self->{boundary}\n" if $self->{attach_diff};
1525 2662 100       33106 print $out "Content-Type: $ctype; charset=$self->{encoding}\n",
1526             ($self->{language} ? "Content-Language: $self->{language}\n" : ()),
1527             "Content-Transfer-Encoding: 8bit\n\n";
1528 2662         8144 return $self;
1529             }
1530              
1531             ##############################################################################
1532              
1533             =head3 start_body
1534              
1535             $notifier->start_body($file_handle);
1536              
1537             This method starts the body of the notification message, which means that it
1538             outputs the contents of the C
attribute, if there are any. Otherwise
1539             it outputs nothing, but see subclasses for other behaviors.
1540              
1541             =cut
1542              
1543             sub start_body {
1544 1585     1585 1 9747 my ($self, $out) = @_;
1545 1585 100       10707 my $start = [ $self->{header} ? ("$self->{header}\n") : () ];
1546 1585         4408 $start = $self->run_filters( start_body => $start );
1547 1585 100 66     34841 print $out @$start, "\n" if $start && @$start;
1548 1585         3838 return $self;
1549             }
1550              
1551             ##############################################################################
1552              
1553             =head3 output_metadata
1554              
1555             $notifier->output_metadata($file_handle);
1556              
1557             This method outputs the metadata of the commit, including the revision number,
1558             author (user), and date of the revision. If the C or
1559             C attributes have been set, then the appropriate URL(s) for the
1560             revision will also be output.
1561              
1562             =cut
1563              
1564             sub output_metadata {
1565 1610     1610 1 4113 my ($self, $out) = @_;
1566 1610         9164 my @lines = ("Revision: $self->{revision}\n");
1567 1610 100       6044 if (my $url = $self->{revision_url}) {
1568 103         1637 push @lines, sprintf " $url\n", $self->{revision};
1569             }
1570              
1571             # Output the Author any any relevant URL.
1572 1610         11333 push @lines, "Author: $self->{user}\n";
1573 1610 100       6769 if (my $url = $self->{author_url}) {
1574 32         1408 push @lines, sprintf " $url\n", $self->{user};
1575             }
1576              
1577 1610         6294 push @lines, "Date: $self->{date}\n";
1578              
1579 1610         4246 print $out @{ $self->run_filters( metadata => \@lines ) };
  1610         75784  
1580 1610         5378 return $self;
1581             }
1582              
1583             ##############################################################################
1584              
1585             =head3 output_log_message
1586              
1587             $notifier->output_log_message($file_handle);
1588              
1589             Outputs the commit log message, as well as the label "Log Message".
1590              
1591             =cut
1592              
1593             sub output_log_message {
1594 1585     1585 1 3465 my ($self, $out) = @_;
1595 1585 100       5718 $self->_dbpnt( "Outputting log message") if $self->{verbose} > 1;
1596 1585         6344 my $msg = join "\n", @{
1597 1585         3130 $self->run_filters( log_message => $self->{message} )
1598             };
1599              
1600 1585         17030 print $out "Log Message:\n-----------\n$msg\n";
1601              
1602             # Make Revision links.
1603 1585 100       9960 if (my $url = $self->{revision_url}) {
1604 103 50       12730 if (my @matches = $msg =~ /\b(?:(?:rev(?:ision)?\s*#?\s*|r)(\d+))\b/ig) {
1605 103         1595693 print $out "\nRevision Links:\n--------------\n";
1606 103         3340 printf $out " $url\n", $_ for @matches;
1607             }
1608             }
1609              
1610             # Make ticketing system links.
1611 1585 100       42791 if (my $map = $self->ticket_map) {
1612 26         702 my $has_header = 0;
1613             $self->run_ticket_map( sub {
1614 156     156   598 my ($regex, $url) = @_;
1615 156         16614 while ($msg =~ /$regex/ig) {
1616 234 100       1118 unless ($has_header) {
1617 26         520 print $out "\nTicket Links:\n------------\n";
1618 26         78 $has_header = 1;
1619             }
1620 234   66     10322 printf $out " $url\n", $2 || $1;
1621             }
1622 26         1638 } );
1623             }
1624              
1625 1585         6012 return $self;
1626             }
1627              
1628             ##############################################################################
1629              
1630             =head3 output_file_lists
1631              
1632             $notifier->output_file_lists($file_handle);
1633              
1634             Outputs the lists of modified, added, and deleted files, as well as the list
1635             of files for which properties were changed. The labels used for each group are
1636             pulled in from the C class method.
1637              
1638             =cut
1639              
1640             sub output_file_lists {
1641 1607     1607 1 4875 my ($self, $out) = @_;
1642 1607 50       116824 my $files = $self->{files} or return $self;
1643 1607 100       10871 $self->_dbpnt( "Outputting file lists") if $self->{verbose} > 1;
1644 1607         20989 my $map = $self->file_label_map;
1645             # Create the underlines.
1646 1607         38211 my %dash = ( map { $_ => '-' x length($map->{$_}) } keys %$map );
  6428         69422  
1647              
1648 1607         39967 foreach my $type (qw(U A D _)) {
1649             # Skip it if there's nothing to report.
1650 6428 100       25251 next unless $files->{$type};
1651 6059 50       13761 $self->_dbpnt( " Outputting $map->{$type} file list")
1652             if $self->{verbose} > 2;
1653              
1654             # Identify the action and output each file.
1655 6059         14569 print $out "\n", @{ $self->run_filters(
  31287         803860  
1656             file_lists => [
1657             "$map->{$type}:\n",
1658             "$dash{$type}\n",
1659 6059         25344 map { " $_\n" } @{ $files->{$type} }
  6059         103208  
1660             ]
1661             ) };
1662             }
1663 1607         4895 print $out "\n";
1664 1607         6580 return $self;
1665             }
1666              
1667             ##############################################################################
1668              
1669             =head3 end_body
1670              
1671             $notifier->end_body($file_handle);
1672              
1673             Closes out the body of the email by outputting the contents of the C
1674             attribute, if any, and then a couple of newlines. Designed to be called when
1675             the body of the message is complete, and before any call to
1676             C.
1677              
1678             =cut
1679              
1680             sub end_body {
1681 1576     1576 1 5575 my ($self, $out) = @_;
1682 1576 50       6325 $self->_dbpnt( "Ending body") if $self->{verbose} > 2;
1683 1576 100       11157 my $end = [ $self->{footer} ? ("$self->{footer}\n") : () ];
1684 1576         12177 $end = $self->run_filters( end_body => $end );
1685 1576 100 66     28124 print $out @$end, "\n" if $end && @$end;
1686 1576         6764 return $self;
1687             }
1688              
1689             ##############################################################################
1690              
1691             =head3 output_diff
1692              
1693             $notifier->output_diff($out_file_handle, $diff_file_handle);
1694              
1695             Reads diff data from C<$diff_file_handle> and outputs it to to
1696             C<$out_file_handle>.
1697              
1698             =cut
1699              
1700             sub output_diff {
1701 228     228 1 1321 my $self = shift;
1702 228 50       4400 $self->_dbpnt( "Outputting diff") if $self->{verbose} > 1;
1703 228         5035 $self->_dump_diff(@_);
1704             }
1705              
1706             ##############################################################################
1707              
1708             =head3 output_attached_diff
1709              
1710             $notifier->output_attached_diff($out_file_handle, $diff_file_handle);
1711              
1712             Reads diff data from C<$diff_file_handle> and outputs it to to
1713             C<$out_file_handle> as an attachment.
1714              
1715             =cut
1716              
1717             sub output_attached_diff {
1718 155     155 1 2046 my ($self, $out, $diff) = @_;
1719 155 50       3786 $self->_dbpnt( "Attaching diff") if $self->{verbose} > 2;
1720 155 100       8976 print $out "\n--$self->{boundary}\n",
1721             "Content-Disposition: attachment; filename=",
1722             "r$self->{revision}-$self->{user}.diff\n",
1723             "Content-Type: $self->{diff_content_type}; charset=$self->{encoding}\n",
1724             ($self->{language} ? "Content-Language: $self->{language}\n" : ()),
1725             "Content-Transfer-Encoding: 8bit\n\n";
1726 155         3412 $self->_dump_diff($out, $diff);
1727             }
1728              
1729             ##############################################################################
1730              
1731             =head3 end_message
1732              
1733             $notifier->end_message($file_handle);
1734              
1735             Outputs the final part of the message,. In this case, that means only a
1736             boundary if the C parameter is true. Designed to be called after
1737             any call to C.
1738              
1739             =cut
1740              
1741             sub end_message {
1742 2633     2633 1 11006 my ($self, $out) = @_;
1743 2633 100       11869 print $out "--$self->{boundary}--\n" if $self->{attach_diff};
1744 2633         11057 return $self;
1745             }
1746              
1747             ##############################################################################
1748              
1749             =head3 run_ticket_map
1750              
1751             $notifier->run_ticket_map( \&callback, @params );
1752              
1753             Loops over the ticket systems you have defined, calling the C<$callback>
1754             function for each one, passing to it the regex, url and @params specified as
1755             its parameters.
1756              
1757             =cut
1758              
1759             sub run_ticket_map {
1760 65     65 1 789 my ($self, $callback, @params) = @_;
1761              
1762             # Make ticketing system links.
1763 65 50       1158 my $map = $self->ticket_map or return;
1764 65         471 my $has_header = 0;
1765 65         4082 while (my ($regex, $url) = each %$map) {
1766 332   66     3816 $regex = $_ticket_regexen{ $regex } || $regex;
1767 332         1732 $callback->( $regex, $url, @params );
1768             }
1769             }
1770              
1771             ##############################################################################
1772              
1773             =head3 run_filters
1774              
1775             $data = $notifier->run_filters( $output_type => $data );
1776              
1777             Runs the filters for C<$output_type> on $data. Used internally by SVN::Notify
1778             and by subclasses.
1779              
1780             =cut
1781              
1782             sub run_filters {
1783 38181     38181 1 232512 my ($self, $type, $data) = @_;
1784 38181 100       680354 my $filters = $self->{filters}{$type} or return $data;
1785 1329         197910 $data = $_->($self, $data) for @$filters;
1786 1329         5069559 return $data;
1787             }
1788              
1789             ##############################################################################
1790              
1791             =head3 filters_for
1792              
1793             my $filters = $notifier->filters_for( $output_type );
1794              
1795             Returns an array reference of of the filters loaded for C<$output_type>.
1796             Returns C if there are no filters have been loaded for C<$output_type>.
1797              
1798             =cut
1799              
1800             sub filters_for {
1801 3579     3579 1 38051 shift->{filters}{+shift};
1802             }
1803              
1804             ##############################################################################
1805              
1806             =head3 diff_handle
1807              
1808             my $diff = $notifier->diff_handle;
1809             while (<$diff>) { print }
1810              
1811             Returns a file handle reference providing access to the the commit diff. It
1812             will usually be passed as the second argument to C or
1813             C.
1814              
1815             =cut
1816              
1817             sub diff_handle {
1818 704     704 1 3678 my $self = shift;
1819             # To avoid svnlook output except for diff contents, such as "Modified"
1820             # etc., to be output in the localized string encoded with another encoding
1821             # from diff contents. HTML and HTML::ColorDiff also expect the terms
1822             # printed in English.
1823 704         17735 local $ENV{LANG} = 'C';
1824              
1825 48 50       49392 return $self->_pipe(
1826             $self->{diff_encoding},
1827             '-|' => $self->{svnlook},
1828             'diff' => $self->{repos_path},
1829             '-r' => $self->{revision},
1830             ( $self->{diff_switches}
1831 704 100       11184 ? grep { defined && $_ ne '' }
1832             # Allow quoting of arguments, but strip out the quotes.
1833             split /(?:'([^']+)'|"([^"]+)")?\s+(?:'([^']+)'|"([^"]+)")?/,
1834             $self->{diff_switches}
1835             : ()
1836             ),
1837             );
1838             }
1839              
1840             ##############################################################################
1841             # This method actually dumps the output of C. It's a separate
1842             # method because output_attached_diff() and output_diff() do essentially the
1843             # same thing, so they can both call it. The diff output will be truncated at
1844             # max_diff_length, if specified.
1845             ##############################################################################
1846              
1847             sub _dump_diff {
1848 383     383   5992 my ($self, $out, $diff) = @_;
1849 383         6991 $diff = $self->run_filters( diff => $diff );
1850              
1851 383 100       3786 if (my $max = $self->{max_diff_length}) {
1852 12         540 my $length = 0;
1853 12         19959019 while (<$diff>) {
1854 288         2650 s/[\n\r]+$//;
1855 288 100       860 if (($length += length) < $max) {
1856 276         2152 print $out $_, "\n";
1857             }
1858             else {
1859 12         207 print $out
1860             "\n\@\@ Diff output truncated at $max characters. \@\@\n";
1861 12         82 last;
1862             }
1863             }
1864             }
1865              
1866             else {
1867 371         2077771974 while (<$diff>) {
1868 12765         190786438 s/[\n\r]+$//;
1869 12765         624456 print $out $_, "\n";
1870             }
1871             }
1872 383 50       446309 close $diff or warn "Child process exited: $?\n";
1873 383         13080 return $self;
1874             }
1875              
1876             ##############################################################################
1877              
1878             __PACKAGE__->_accessors(qw(
1879             repos_path
1880             revision
1881             to_regex_map
1882             to_email_map
1883             from
1884             user_domain
1885             svnlook
1886             sendmail
1887             set_sender
1888             add_headers
1889             smtp
1890             encoding
1891             diff_encoding
1892             svn_encoding
1893             env_lang
1894             svn_env_lang
1895             language
1896             with_diff
1897             attach_diff
1898             diff_switches
1899             reply_to
1900             subject_prefix
1901             subject_cx
1902             max_sub_length
1903             max_diff_length
1904             author_url
1905             revision_url
1906             ticket_url
1907             ticket_regex
1908             ticket_map
1909             header
1910             footer
1911             verbose
1912             boundary
1913             user
1914             date
1915             message
1916             message_size
1917             subject
1918             files
1919             ));
1920              
1921             ##############################################################################
1922             # This method is used to create accessors for the list of attributes passed to
1923             # it. It creates them both for SVN::Notify (just above) and for all subclasses
1924             # in register_attributes().
1925             ##############################################################################
1926              
1927             sub _accessors {
1928 562     562   1484 my $class = shift;
1929 562         2083 for my $attr (@_) {
1930 293     293   5214 no strict 'refs';
  293         628  
  293         108750  
1931 12385         60476 *{"$class\::$attr"} = sub {
1932 32782     32782   103675 my $self = shift;
1933 32782 100       2456940 return $self->{$attr} unless @_;
1934 53         317 $self->{$attr} = shift;
1935 53         305 return $self;
1936 12385         34703 };
1937             }
1938             }
1939              
1940             # Aliases for deprecated attributes.
1941 84     84 1 420 sub svnweb_url { shift->revision_url(@_) }
1942 84     84 1 420 sub viewcvs_url { shift->revision_url(@_) }
1943 0     0 1 0 sub charset { shift->encoding(@_) }
1944              
1945             # Deprecated ticket URL systems.
1946             for my $tick (qw(rt bugzilla jira gnats)) {
1947 293     293   1847 no strict 'refs';
  293         702  
  293         88657  
1948             *{$tick . '_url'} = sub {
1949 156     156   312 my $self = shift;
1950 156   50     1560 my $map = $self->{ticket_map} || {};
1951 156 50       1196 return $map->{$tick} unless @_;
1952 0 0       0 if (my $url = shift) {
1953 0         0 $map->{$tick} = $url;
1954             } else {
1955 0         0 delete $map->{$tick};
1956             }
1957 0         0 $self->{ticket_map} = $map;
1958 0         0 return $self;
1959             };
1960             }
1961              
1962             for my $attr (qw(to strip_cx_regex)) {
1963 293     293   2410 no strict 'refs';
  293         673  
  293         487729  
1964             *{__PACKAGE__ . "::$attr"} = sub {
1965 117     117   281 my $self = shift;
1966 117 50       973 return wantarray ? @{ $self->{$attr} } : $self->{$attr}[0] unless @_;
  117 50       2105  
1967 0         0 $self->{$attr}= \@_;
1968 0         0 return $self;
1969             };
1970             }
1971              
1972             =head2 Accessors
1973              
1974             =head3 repos_path
1975              
1976             my $repos_path = $notifier->repos_path;
1977             $notifier = $notifier->repos_path($repos_path);
1978              
1979             Gets or sets the value of the C attribute.
1980              
1981             =head3 revision
1982              
1983             my $revision = $notifier->revision;
1984             $notifier = $notifier->revision($revision);
1985              
1986             Gets or sets the value of the C attribute.
1987              
1988             =head3 to
1989              
1990             my $to = $notifier->to;
1991             $notifier = $notifier->to($to);
1992             my @tos = $notifier->to;
1993             $notifier = $notifier->to(@tos);
1994              
1995             Gets or sets the list of values stored in the C attribute. In a scalar
1996             context, it returns only the first value in the list, for backwards
1997             compatibility with older versions of SVN::Notify. In list context, it of
1998             course returns the entire list. Pass in one or more values to set all of the
1999             values for the C attribute.
2000              
2001             =head3 to_regex_map
2002              
2003             my $to_regex_map = $notifier->to_regex_map;
2004             $notifier = $notifier->to_regex_map($to_regex_map);
2005              
2006             Gets or sets the value of the C attribute, which is a hash
2007             reference of email addresses mapped to regular expressions.
2008              
2009             =head3 to_email_map
2010              
2011             my $to_email_map = $notifier->to_email_map;
2012             $notifier = $notifier->to_email_map($to_email_map);
2013              
2014             Gets or sets the value of the C attribute, which is a hash
2015             reference of regular expressions mapped to email addresses.
2016              
2017             =head3 from
2018              
2019             my $from = $notifier->from;
2020             $notifier = $notifier->from($from);
2021              
2022             Gets or sets the value of the C attribute.
2023              
2024             =head3 user_domain
2025              
2026             my $user_domain = $notifier->user_domain;
2027             $notifier = $notifier->user_domain($user_domain);
2028              
2029             Gets or sets the value of the C attribute.
2030              
2031             =head3 svnlook
2032              
2033             my $svnlook = $notifier->svnlook;
2034             $notifier = $notifier->svnlook($svnlook);
2035              
2036             Gets or sets the value of the C attribute.
2037              
2038             =head3 sendmail
2039              
2040             my $sendmail = $notifier->sendmail;
2041             $notifier = $notifier->sendmail($sendmail);
2042              
2043             Gets or sets the value of the C attribute.
2044              
2045             =head3 set_sender
2046              
2047             my $set_sender = $notifier->set_sender;
2048             $notifier = $notifier->set_sender($set_sender);
2049              
2050             Gets or sets the value of the C attribute.
2051              
2052             =head3 smtp
2053              
2054             my $smtp = $notifier->smtp;
2055             $notifier = $notifier->smtp($smtp);
2056              
2057             Gets or sets the value of the C attribute.
2058              
2059             =head3 encoding
2060              
2061             my $encoding = $notifier->encoding;
2062             $notifier = $notifier->encoding($encoding);
2063              
2064             Gets or sets the value of the C attribute. C is an alias
2065             preserved for backward compatibility.
2066              
2067             =head3 svn_encoding
2068              
2069             my $svn_encoding = $notifier->svn_encoding;
2070             $notifier = $notifier->svn_encoding($svn_encoding);
2071              
2072             Gets or sets the value of the C attribute.
2073              
2074             =head3 diff_encoding
2075              
2076             my $diff_encoding = $notifier->diff_encoding;
2077             $notifier = $notifier->diff_encoding($diff_encoding);
2078              
2079             Gets or sets the value of the C attribute.
2080              
2081             =head3 language
2082              
2083             my $language = $notifier->language;
2084             $notifier = $notifier->language($language);
2085              
2086             Gets or sets the value of the C attribute.
2087              
2088             =head3 env_lang
2089              
2090             my $env_lang = $notifier->env_lang;
2091             $notifier = $notifier->env_lang($env_lang);
2092              
2093             Gets or sets the value of the C attribute, which is set to C<<
2094             $notify->language . '.' . $notify->encoding >> when C is set, and
2095             otherwise is C. This attribute is used to set the C<$LANG> environment
2096             variable, if it is not already set by the environment, before executing
2097             C.
2098              
2099             =head3 svn_env_lang
2100              
2101             my $svn_env_lang = $notifier->svn_env_lang;
2102             $notifier = $notifier->svn_env_lang($svn_env_lang);
2103              
2104             Gets or sets the value of the C attribute, which is set to C<<
2105             $notify->language . '.' . $notify->svn_encoding >> when C is set,
2106             and otherwise is C. This attribute is used to set the C<$LANG>
2107             environment variable, if it is not already set by the environment, before
2108             executing C. It is not used for C, however, as the diff
2109             itself will be emitted in raw octets except for headers such as "Modified",
2110             which need to be in English so that subclasses can parse them. Thus, C<$LANG>
2111             is always set to "C" for the execution of C.
2112              
2113             =head3 with_diff
2114              
2115             my $with_diff = $notifier->with_diff;
2116             $notifier = $notifier->with_diff($with_diff);
2117              
2118             Gets or sets the value of the C attribute.
2119              
2120             =head3 attach_diff
2121              
2122             my $attach_diff = $notifier->attach_diff;
2123             $notifier = $notifier->attach_diff($attach_diff);
2124              
2125             Gets or sets the value of the C attribute.
2126              
2127             =head3 diff_switches
2128              
2129             my $diff_switches = $notifier->diff_switches;
2130             $notifier = $notifier->diff_switches($diff_switches);
2131              
2132             Gets or sets the value of the C attribute.
2133              
2134             =head3 reply_to
2135              
2136             my $reply_to = $notifier->reply_to;
2137             $notifier = $notifier->reply_to($reply_to);
2138              
2139             Gets or sets the value of the C attribute.
2140              
2141             =head3 add_headers
2142              
2143             my $add_headers = $notifier->add_headers;
2144             $notifier = $notifier->add_headers({
2145             'X-Accept' => [qw(This That)],
2146             'X-Reject' => 'Me!',
2147             });
2148              
2149             Gets or sets the value of the C attribute, which is a hash
2150             reference of the headers to be added to the email message. If one header needs
2151             to appear multiple times, simply pass the corresponding hash value as an array
2152             reference of each value for the header. Not to be confused with the C
2153             accessor, which gets and sets text to be included at the beginning of the body
2154             of the email message.
2155              
2156             =head3 subject_prefix
2157              
2158             my $subject_prefix = $notifier->subject_prefix;
2159             $notifier = $notifier->subject_prefix($subject_prefix);
2160              
2161             Gets or sets the value of the C attribute.
2162              
2163             =head3 subject_cx
2164              
2165             my $subject_cx = $notifier->subject_cx;
2166             $notifier = $notifier->subject_cx($subject_cx);
2167              
2168             Gets or sets the value of the C attribute.
2169              
2170             =head3 strip_cx_regex
2171              
2172             my $strip_cx_regex = $notifier->strip_cx_regex;
2173             $notifier = $notifier->strip_cx_regex($strip_cx_regex);
2174             my @strip_cx_regexs = $notifier->strip_cx_regex;
2175             $notifier = $notifier->strip_cx_regex(@strip_cx_regexs);
2176              
2177             Gets or sets the list of values stored in the C attribute. In
2178             a scalar context, it returns only the first value in the list; in list
2179             context, it of course returns the entire list. Pass in one or more values to
2180             set all of the values for the C attribute.
2181              
2182             =head3 max_sub_length
2183              
2184             my $max_sub_length = $notifier->max_sub_length;
2185             $notifier = $notifier->max_sub_length($max_sub_length);
2186              
2187             Gets or sets the value of the C attribute.
2188              
2189             =head3 max_diff_length
2190              
2191             my $max_diff_length = $notifier->max_diff_length;
2192             $notifier = $notifier->max_diff_length($max_diff_length);
2193              
2194             Gets or set the value of the C attribute.
2195              
2196             =head3 author_url
2197              
2198             my $author_url = $notifier->author_url;
2199             $notifier = $notifier->author_url($author_url);
2200              
2201             Gets or sets the value of the C attribute.
2202              
2203             =head3 revision_url
2204              
2205             my $revision_url = $notifier->revision_url;
2206             $notifier = $notifier->revision_url($revision_url);
2207              
2208             Gets or sets the value of the C attribute.
2209              
2210             =head3 svnweb_url
2211              
2212             Deprecated. Pleas use C, instead.
2213              
2214             =head3 viewcvs_url
2215              
2216             Deprecated. Pleas use C, instead.
2217              
2218             =head3 verbose
2219              
2220             my $verbose = $notifier->verbose;
2221             $notifier = $notifier->verbose($verbose);
2222              
2223             Gets or sets the value of the C attribute.
2224              
2225             =head3 boundary
2226              
2227             my $boundary = $notifier->boundary;
2228             $notifier = $notifier->boundary($boundary);
2229              
2230             Gets or sets the value of the C attribute. This string is normally
2231             set by a call to C, but may be set ahead of time.
2232              
2233             =head3 user
2234              
2235             my $user = $notifier->user;
2236             $notifier = $notifier->user($user);
2237              
2238             Gets or sets the value of the C attribute, which is set to the value
2239             pulled in from F by the call to C.
2240              
2241             =head3 date
2242              
2243             my $date = $notifier->date;
2244             $notifier = $notifier->date($date);
2245              
2246             Gets or sets the value of the C attribute, which is set to the value
2247             pulled in from F by the call to C.
2248              
2249             =head3 message
2250              
2251             my $message = $notifier->message;
2252             $notifier = $notifier->message($message);
2253              
2254             Gets or sets the value of the C attribute, which is set to an array
2255             reference of strings by the call to C.
2256              
2257             =head3 message_size
2258              
2259             my $message_size = $notifier->message_size;
2260             $notifier = $notifier->message_size($message_size);
2261              
2262             Gets or sets the value of the C attribute, which is set to the
2263             value pulled in from F by the call to C.
2264              
2265             =head3 subject
2266              
2267             my $subject = $notifier->subject;
2268             $notifier = $notifier->subject($subject);
2269              
2270             Gets or sets the value of the C attribute, which is normally set
2271             by a call to C, but may be set explicitly.
2272              
2273             =head3 files
2274              
2275             my $files = $notifier->files;
2276             $notifier = $notifier->files($files);
2277              
2278             Gets or sets the value of the C attribute, which is set to a hash
2279             reference of change type mapped to arrays of strings by the call to
2280             C.
2281              
2282             =head3 header
2283              
2284             my $header = $notifier->header;
2285             $notifier = $notifier->header($header);
2286              
2287             Gets or set the value of the C
attribute. Not to be confused with the
2288             C attribute, which manages headers to be inserted into the
2289             notification email message headers.
2290              
2291             =head3 footer
2292              
2293             my $footer = $notifier->footer;
2294             $notifier = $notifier->footer($footer);
2295              
2296             Gets or set the value of the C
attribute.
2297              
2298             =cut
2299              
2300             ##############################################################################
2301             # This method forks off a process to execute an external program and any
2302             # associated arguments and returns a file handle that can be read from to
2303             # fetch the output of the external program, or written to. Pass "-|" as the
2304             # sole argument to read from another process (such as svnlook), and pass "|-"
2305             # to write to another process (such as sendmail).
2306             ##############################################################################
2307              
2308             sub _pipe {
2309 9133     9133   47437 my ($self, $encode, $mode) = (shift, shift, shift);
2310 9133 100       48956 $self->_dbpnt( q{Piping execution of "} . join(q{" "}, @_) . q{"})
2311             if $self->{verbose};
2312             # Safer version of backtick (see perlipc(1)).
2313 9133         38617 local *PIPE;
2314 9133         10715 if (WIN32) {
2315             my $cmd = $mode eq '-|'
2316             ? q{"} . join(q{" "}, @_) . q{"|}
2317             : q{|"} . join(q{" "}, @_) . q{"};
2318             open PIPE, $cmd or die "Cannot fork: $!\n";
2319             binmode PIPE, ":encoding($encode)" if PERL58 && $encode;
2320             return *PIPE;
2321             }
2322              
2323 9133         22883861 my $pid = open PIPE, $mode;
2324 9133 50       599706 die "Cannot fork: $!\n" unless defined $pid;
2325              
2326 9133 100       332832 if ($pid) {
2327             # Parent process. Set the encoding layer and return the file handle.
2328 8848 50   326   2363961 binmode PIPE, ":encoding($encode)" if PERL58 && $encode;
  326         14044  
  326         2242  
  326         23123  
2329 8848         7120043 return *PIPE;
2330             } else {
2331             # Child process. Execute the commands.
2332 285 0       0 exec @_ or die "Cannot exec $_[0]: $!\n";
2333             # Not reached.
2334             }
2335             }
2336              
2337             ##############################################################################
2338             # This method passes its arguments to _pipe(), but then fetches each line
2339             # off output from the returned file handle, safely strips out and replaces any
2340             # newlines and carriage returns, and returns an array reference of those
2341             # lines.
2342             ##############################################################################
2343              
2344             sub _read_pipe {
2345 2793     2793   6202 my $self = shift;
2346 2793         17416 my $fh = $self->_pipe( $self->{svn_encoding}, '-|', @_ );
2347 2709         92441 local $/; my @lines = split /(?:\r\n|\r|\n)/, <$fh>;
  2709         9352496425  
2348 2709 50       3583100 close $fh or warn "Child process exited: $?\n";
2349 2709         73952 return \@lines;
2350             }
2351              
2352             ##############################################################################
2353             # This method is used for debugging output in various verbose modes.
2354             ##############################################################################
2355              
2356 0     0   0 sub _dbpnt { print ref(shift), ': ', join ' ', @_; }
2357              
2358             ##############################################################################
2359             # This function is used to exit the program with an error if a parameter is
2360             # missing.
2361             ##############################################################################
2362              
2363             sub _usage {
2364 3     3   9 my ($msg) = @_;
2365              
2366             # Just die if the API is used.
2367 3 50       54 die $msg if $0 !~ /\bsvnnotify(?:[.]bat)?$/;
2368              
2369             # Otherwise, tell 'em how to use it.
2370 0         0 $msg =~ s/_/-/g;
2371 0         0 $msg =~ s/(\s+")/$1--/g;
2372 0         0 $msg =~ s/\bparameter\b/option/g;
2373 0         0 require Pod::Usage;
2374 0         0 Pod::Usage::pod2usage(
2375             '-message' => $msg,
2376             '-verbose' => 99,
2377             '-sections' => '(?i:(Usage|Options))',
2378             '-exitval' => 1,
2379             );
2380             }
2381              
2382             package SVN::Notify::SMTP;
2383              
2384             sub get_handle {
2385 4     4   39 my ($class, $notifier) = @_;
2386              
2387             # Load Net::SMTP::TLS.
2388 4         195 require Net::SMTP::TLS;
2389 4         49686 require Sys::Hostname;
2390 4 100       8565 my $smtp = Net::SMTP::TLS->new(
    50          
    100          
    100          
    100          
    50          
2391             $notifier->{smtp},
2392             Hello => Sys::Hostname::hostname(),
2393             ( $notifier->{smtp_port} ? ( Port => $notifier->{smtp_port} ) : () ),
2394             ( $notifier->{smtp_tls} ? () : (NoTLS => 1) ),
2395             ( $notifier->{smtp_user} ? ( User => $notifier->{smtp_user} ) : () ),
2396             ( $notifier->{smtp_pass} ? ( Password => $notifier->{smtp_pass} ) : () ),
2397             ( $notifier->{verbose} ? ( Debug => 1 ) : () )
2398             ) or die "Unable to create SMTP object: $!";
2399              
2400 4         383 $smtp->mail($notifier->{from});
2401 4         40 $smtp->to(map { split /\s*,\s*/ } @{ $notifier->{to} });
  8         73  
  4         32  
2402 4         55 $smtp->data;
2403 4         84 tie local(*SMTP), $class, $smtp, $notifier;
2404             # Perl 5.6 requires the escape.
2405 4         44 return SVN::Notify::PERL58 ? *SMTP : \*SMTP;
2406             }
2407              
2408             sub TIEHANDLE {
2409 4     4   12 my ($class, $smtp, $notifier) = @_;
2410 4         128 bless { smtp => $smtp, notifier => $notifier }, $class;
2411             }
2412              
2413             sub PRINT {
2414 44     44   81 my $self = shift;
2415 44         53 if (SVN::Notify::PERL58) {
2416 44         204 my $encode = $self->{notifier}->encoding;
2417 196         7519 return $self->{smtp}->datasend( map {
2418 44         176 Encode::encode( $encode, $_ )
2419             } @_ )
2420             }
2421 0         0 return $self->{smtp}->datasend(@_);
2422             }
2423              
2424             sub PRINTF {
2425 4     4   14 my $self = shift;
2426 4         43 $self->PRINT( sprintf(shift, @_) );
2427             }
2428              
2429             sub CLOSE {
2430 4     4   11 my $self = shift;
2431 4         16 $self->{smtp}->dataend;
2432 4         35 $self->{smtp}->quit;
2433             }
2434              
2435             1;
2436             __END__