File Coverage

blib/lib/Sietima.pm
Criterion Covered Total %
statement 83 92 90.2
branch 14 28 50.0
condition 2 3 66.6
subroutine 19 21 90.4
pod 7 7 100.0
total 125 151 82.7


line stmt bran cond sub pod time code
1             package Sietima;
2 16     16   3896737 use Moo;
  16         51  
  16         147  
3 16     16   12320 use Sietima::Policy;
  16         59  
  16         250  
4 16     16   8890 use Types::Standard qw(ArrayRef Object FileHandle Maybe);
  16         1202614  
  16         210  
5 16     16   31511 use Type::Params qw(compile);
  16         189961  
  16         170  
6 16         245 use Sietima::Types qw(Address AddressFromStr
7             EmailMIME Message
8             Subscriber SubscriberFromAddress SubscriberFromStr SubscriberFromHashRef
9 16     16   11300 Transport);
  16         89  
10 16     16   34785 use Sietima::Message;
  16         87  
  16         691  
11 16     16   170 use Sietima::Subscriber;
  16         46  
  16         366  
12 16     16   93 use Email::Sender::Simple qw();
  16         38  
  16         292  
13 16     16   166 use Email::Sender;
  16         35  
  16         421  
14 16     16   90 use Email::Address;
  16         40  
  16         417  
15 16     16   84 use namespace::clean;
  16         47  
  16         101  
16              
17             with 'MooX::Traits';
18             our $VERSION = '1.0.3'; # VERSION
19             # ABSTRACT: minimal mailing list manager
20              
21              
22             has return_path => (
23             isa => Address,
24             is => 'ro',
25             required => 1,
26             coerce => AddressFromStr,
27             );
28              
29              
30             my $subscribers_array = ArrayRef[
31             Subscriber->plus_coercions(
32             SubscriberFromAddress,
33             SubscriberFromStr,
34             SubscriberFromHashRef,
35             )
36             ];
37             has subscribers => (
38             isa => $subscribers_array,
39             is => 'lazy',
40             coerce => $subscribers_array->coercion,
41             );
42 1     1   68 sub _build_subscribers { +[] }
43              
44              
45             has transport => (
46             isa => Transport,
47             is => 'lazy',
48             );
49 0     0   0 sub _build_transport { Email::Sender::Simple->default_transport }
50              
51              
52 0 0   0 1 0 sub handle_mail_from_stdin($self,@) {
  0         0  
  0         0  
53 0         0 my $mail_text = do { local $/; <> };
  0         0  
  0         0  
54             # we're hoping that, since we probably got called from an MTA/MDA,
55             # STDIN contains a well-formed email message, addressed to us
56 0         0 my $incoming_mail = Email::MIME->new(\$mail_text);
57 0         0 return $self->handle_mail($incoming_mail);
58             }
59              
60              
61 29 50   29 1 324068 sub handle_mail($self,$incoming_mail) {
  29 50       126  
  29         76  
  29         89  
  29         64  
62 29         141 state $check = compile(Object,EmailMIME); $check->(@_);
  29         17736  
63              
64 29         1274 my (@outgoing_messages) = $self->munge_mail($incoming_mail);
65 29         38133 for my $outgoing_message (@outgoing_messages) {
66 24         201 $self->send_message($outgoing_message);
67             }
68 29         203 return;
69             }
70              
71              
72 22 50   22 1 428 sub subscribers_to_send_to($self,$incoming_mail) {
  22 50       87  
  22         56  
  22         45  
  22         87  
73 22         71 state $check = compile(Object,EmailMIME); $check->(@_);
  22         11220  
74              
75 22         843 return $self->subscribers;
76             }
77              
78              
79 22 50   22 1 691 sub munge_mail($self,$incoming_mail) {
  22 50       98  
  22         59  
  22         51  
  22         44  
80 22         72 state $check = compile(Object,EmailMIME); $check->(@_);
  22         11525  
81              
82 22         353 return Sietima::Message->new({
83             mail => $incoming_mail,
84             from => $self->return_path,
85             to => $self->subscribers_to_send_to($incoming_mail),
86             });
87             }
88              
89              
90 24 50   24 1 120 sub send_message($self,$outgoing_message) {
  24 50       93  
  24         62  
  24         52  
  24         84  
91 24         91 state $check = compile(Object,Message); $check->(@_);
  24         14682  
92              
93 24         384 my $envelope = $outgoing_message->envelope;
94 24 100 66     933 if ($envelope->{to} && $envelope->{to}->@*) {
95 23         521 $self->transport->send(
96             $outgoing_message->mail,
97             $envelope,
98             );
99             }
100              
101 24         6023 return;
102             }
103              
104 18     18   142924 sub _trait_namespace { 'Sietima::Role' } ## no critic(ProhibitUnusedPrivateSubroutines)
105              
106              
107 4 50   4 1 86 sub list_addresses($self) {
  4 50       16  
  4         9  
  4         8  
108             return +{
109 4         89 return_path => $self->return_path,
110             };
111             }
112              
113              
114 2 50   2 1 13 sub command_line_spec($self) {
  2 50       10  
  2         5  
  2         5  
115             return {
116 2         20 name => 'sietima',
117             title => 'a simple mailing list manager',
118             subcommands => {
119             send => {
120             op => 'handle_mail_from_stdin',
121             summary => 'send email from STDIN',
122             },
123             },
124             };
125             }
126              
127             1;
128              
129             __END__
130              
131             =pod
132              
133             =encoding UTF-8
134              
135             =head1 NAME
136              
137             Sietima - minimal mailing list manager
138              
139             =head1 VERSION
140              
141             version 1.0.3
142              
143             =head1 SYNOPSIS
144              
145             use Sietima;
146              
147             Sietima->new({
148             return_path => 'the-list@the-domain.tld',
149             subscribers => [ 'person@some.were', @etc ],
150             })->handle_mail_from_stdin;
151              
152             =head1 DESCRIPTION
153              
154             Sietima is a minimal mailing list manager written in modern Perl. It
155             aims to be the spiritual successor of L<Siesta>.
156              
157             The base C<Sietima> class does very little: it just puts the email
158             message from C<STDIN> into a new envelope using L<< /C<return_path> >>
159             as sender and all the L<< /C<subscribers> >> addresses as recipients,
160             and sends it.
161              
162             Additional behaviour is provided via traits / roles. This class
163             consumes L<< C<MooX::Traits> >> to simplify composing roles:
164              
165             Sietima->with_traits(qw(AvoidDups NoMail))->new(\%args);
166              
167             These are the traits provided with the default distribution:
168              
169             =over 4
170              
171             =item L<< C<AvoidDups>|Sietima::Role::AvoidDups >>
172              
173             prevents the sender from receiving copies of their own messages
174              
175             =item L<< C<Debounce>|Sietima::Role::Debounce >>
176              
177             avoids mail-loops using a C<X-Been-There> header
178              
179             =item L<< C<Headers>|Sietima::Role::Headers >>
180              
181             adds C<List-*> headers to all outgoing messages
182              
183             =item L<< C<ManualSubscription>|Sietima::Role::ManualSubscription >>
184              
185             specifies that to (un)subscribe, people should write to the list owner
186              
187             =item L<< C<NoMail>|Sietima::Role::NoMail >>
188              
189             avoids sending messages to subscribers who don't want them
190              
191             =item L<< C<ReplyTo>|Sietima::Role::ReplyTo >>
192              
193             optionally sets the C<Reply-To> header to the mailing list address
194              
195             =item L<< C<SubjectTag>|Sietima::Role::SubjectTag >>
196              
197             prepends a C<[tag]> to the subject header of outgoing messages that
198             aren't already tagged
199              
200             =item L<< C<SubscriberOnly::Drop>|Sietima::Role::SubscriberOnly::Drop >>
201              
202             silently drops all messages coming from addresses not subscribed to
203             the list
204              
205             =item L<< C<SubscriberOnly::Moderate>|Sietima::Role::SubscriberOnly::Moderate >>
206              
207             holds messages coming from addresses not subscribed to the list for
208             moderation, and provides commands to manage the moderation queue
209              
210             =back
211              
212             The only "configuration mechanism" currently supported is to
213             initialise a C<Sietima> object in your driver script, passing all the
214             needed values to the constructor. L<< C<Sietima::CmdLine> >> is the
215             recommended way of doing that: it adds command-line parsing capability
216             to Sietima.
217              
218             =head1 ATTRIBUTES
219              
220             =head2 C<return_path>
221              
222             A L<< C<Email::Address> >> instance, coerced from string if
223             necessary. This is the address that Sietima will send messages
224             I<from>.
225              
226             =head2 C<subscribers>
227              
228             An array-ref of L<< C<Sietima::Subscriber> >> objects, defaults to the
229             empty array.
230              
231             Each item can be coerced from a string or a L<< C<Email::Address> >>
232             instance, or a hashref of the form
233              
234             { address => $string, %other_attributes }
235              
236             The base Sietima class only uses the address of subscribers, but some
237             roles use the other attributes (L<< C<NoMail>|Sietima::Role::NoMail
238             >>, for example, uses the C<prefs> attribute, and L<<
239             C<SubscriberOnly> >> uses C<aliases> via L<<
240             C<match>|Sietima::Subscriber/match >>)
241              
242             =head2 C<transport>
243              
244             A L<< C<Email::Sender::Transport> >> instance, which will be used to
245             send messages. If not passed in, Sietima uses L<<
246             C<Email::Sender::Simple> >>'s L<<
247             C<default_transport>|Email::Sender::Simple/default_transport >>.
248              
249             =head1 METHODS
250              
251             =head2 C<handle_mail_from_stdin>
252              
253             $sietima->handle_mail_from_stdin();
254              
255             This is the main entry-point when Sietima is invoked from a MTA. It
256             will parse a L<< C<Email::MIME> >> object out of the standard input,
257             then pass it to L<< /C<handle_mail> >> for processing.
258              
259             =head2 C<handle_mail>
260              
261             $sietima->handle_mail($email_mime);
262              
263             Main driver method: converts the given email message into a list of
264             L<< C<Sietima::Message> >> objects by calling L<< /C<munge_mail> >>,
265             then sends each of them by calling L<< /C<send_message> >>.
266              
267             =head2 C<subscribers_to_send_to>
268              
269             my $subscribers_aref = $sietima->subscribers_to_send_to($email_mime);
270              
271             Returns an array-ref of L<< C<Sietima::Subscriber> >> objects that
272             should receive copies of the given email message.
273              
274             In this base class, it just returns the value of the L<<
275             /C<subscribers> >> attribute. Roles such as L<<
276             C<AvoidDups>|Sietima::Role::AvoidDups >> modify this method to exclude
277             some subscribers.
278              
279             =head2 C<munge_mail>
280              
281             my @messages = $sietima->munge_mail($email_mime);
282              
283             Returns a list of L<< C<Sietima::Message> >> objects representing the
284             messages to send to subscribers, based on the given email message.
285              
286             In this base class, this method returns a single instance to send to
287             all L<< /C<subscribers_to_send_to> >>, containing exactly the given
288             email message.
289              
290             Roles such as L<< C<SubjectTag>|Sietima::Role::SubjectTag >> modify
291             this method to alter the message.
292              
293             =head2 C<send_message>
294              
295             $sietima->send_message($sietima_message);
296              
297             Sends the given L<< C<Sietima::Message> >> object via the L<<
298             /C<transport> >>, but only if the message's
299             L<envelope|Sietima::Message/envelope> specifies some recipients.
300              
301             =head2 C<list_addresses>
302              
303             my $addresses_href = $sietima->list_addresses;
304              
305             Returns a hashref of L<< C<Sietima::HeaderURI> >> instances (or things
306             that can be passed to its constructor, like L<< C<Email::Address> >>,
307             L<< C<URI> >>, or strings), that declare various addresses related to
308             this list.
309              
310             This base class declares only the L<< /C<return_path> >>, and does not
311             use this method at all.
312              
313             The L<< C<Headers>|Sietima::Role::Headers >> role uses this to
314             populate the various C<List-*> headers.
315              
316             =head2 C<command_line_spec>
317              
318             my $app_spec_data = $sietima->command_line_spec;
319              
320             Returns a hashref describing the command line processing for L<<
321             C<App::Spec> >>. L<< C<Sietima::CmdLine> >> uses this to build the
322             command line parser.
323              
324             This base class declares a single sub-command:
325              
326             =over
327              
328             =item C<send>
329              
330             Invokes the L<< /C<handle_mail_from_stdin> >> method.
331              
332             For example, in a C<.qmail> file:
333              
334             |/path/to/sietima send
335              
336             =back
337              
338             Roles can extend this to provide additional sub-commands and options.
339              
340             =head1 AUTHOR
341              
342             Gianni Ceccarelli <dakkar@thenautilus.net>
343              
344             =head1 COPYRIGHT AND LICENSE
345              
346             This software is copyright (c) 2017 by Gianni Ceccarelli <dakkar@thenautilus.net>.
347              
348             This is free software; you can redistribute it and/or modify it under
349             the same terms as the Perl 5 programming language system itself.
350              
351             =cut