/
Mailbox.pm6
317 lines (226 loc) · 9.31 KB
/
Mailbox.pm6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
use v6;
unit package Email::Address;
use Email::Address::Parser :parse-email-address;
use Email::Address::Format :ALL;
role AddrSpec does Email::Address::Format {
has Str $.local-part is rw is required;
has Str $.domain is rw is required;
method format(--> Str:D) {
join '@', maybe-escape($!local-part), $!domain
}
method Str(--> Str:D) { self.format }
method gist(--> Str:D) { self.format }
}
class AddrSpec::Parsed does AddrSpec {
has Str $.original;
}
subset CommentStr of Str where {
# Only match strings that contain balanced parentheses
my $balanced-parens;
$balanced-parens = regex {
[
| <-[()]>
| '()'
| '(' ~ ')' $balanced-parens
]+
}
!.defined || ?/^ $balanced-parens $/
|| fail "Comments must contains balanced parentheses"
};
role Mailbox does Email::Address::Format {
has Str $.display-name is rw;
has AddrSpec $.address is rw is required;
has CommentStr $.comment is rw;
multi method new(
Str :$display-name,
AddrSpec:D :$address!,
CommentStr :$comment?,
--> Mailbox:D
) {
self.bless(
:$display-name,
:$address,
:$comment,
);
}
multi method new(
Str :$display-name,
Str:D :$address!,
CommentStr :$comment?,
--> Mailbox:D
) {
self.bless(
:$display-name,
:$address,
:$comment,
);
}
multi method new(
Str $display-name,
AddrSpec:D $address,
CommentStr $comment?,
--> Mailbox:D
) {
self.bless(
:$display-name,
:$address,
:$comment,
);
}
multi method new(
Str $display-name,
Str:D $address,
CommentStr $comment?,
--> Mailbox:D
) {
self.bless(
:$display-name,
:$address,
:$comment,
);
}
multi method new(*%_, *@_) {
X::Email::Address::AdHoc.new("email parameters are invalid");
}
multi submethod BUILD(
Str :$!display-name,
AddrSpec:D :$!address,
CommentStr :$!comment,
) {}
multi submethod BUILD(
Str :$!display-name,
Str:D :$address,
CommentStr :$!comment,
) {
$!address = AddrSpec.new(:local-part(''), :domain(''));
self.set-address($address);
}
method local-part(--> Str) is rw { return-rw $!address.local-part }
method domain(--> Str) is rw { return-rw $!address.domain }
method set-address(
Str:D $address,
:$parser = Email::Address::RFC5322-Parser,
:$actions = Email::Address::RFC5322-Actions,
--> AddrSpec
) {
my %addr-spec = parse-email-address($address, :$parser, :$actions, :rule<addr-spec>);
$!address.local-part = %addr-spec<local-part>;
$!address.domain = %addr-spec<domain>;
$!address;
}
method guess-name(--> Str) {
with $!display-name { return $!display-name if $!display-name.chars }
with $!comment { return $!comment if $!comment.chars }
with $.local-part { return $.local-part }
'';
}
method format(--> Str) {
my $address = '';
# quoting can't be used when =?...?...?= mime words are in the name,
# use obsolete RFC822 display name instead in that case. Since we don't
# make any effort to understand or decode these, we assume we'll
# just encounter them as-is but do this one special thing for them
with $!display-name {
if has-mime-word($!display-name) {
$address ~= $!display-name;
}
else {
$address ~= maybe-escape($!display-name);
}
$address ~= " <$!address>";
}
else {
$address ~= $!address;
}
$address ~= " ($!comment)" with $!comment;
$address;
}
method Str(--> Str) { self.format }
method gist(--> Str) { self.format }
}
my class Mailbox::Parsed does Mailbox {
has Str $.original;
}
=begin pod
=head1 NAME
Email::Address::Mailbox - representation of a single email address
=head1 SYNOPSIS
use Email::Address;
my Email::Address::Mailbox $hanson = Email::Address.parse(
q["John Hanson" <jhanson@example.com> (Maryland House of Delegates)]
);
my $boudinot = Email::Address::Mailbox.new(
'Elias Boudinot',
'eb@example.com',
'Commissary of Prisoners (Continental Army)',
);
say $boudinot;
#> "Elias Boudinot" <eb@example.com> (Commissary of Prisoners (Continental Army))
=head1 DESCRIPTION
This class describes the email address representing a single mailbox. This is what most people think of when they here the term "email address."
=head1 METHODS
=head2 method new
multi method new(Str $display-name, Str:D $address, Str $comment? --> Email::Address::Mailbox)
multi method new(Str $display-name, Email::Address::AddrSpec:D $address, Str $comment? --> Email::Address::Mailbox)
multi method new(Str :$display-name, Str:D :$address!, Str :$comment --> Email::Address::Mailbox)
multi method new(Str :$display-name, Email::Address::AddrSpec:D :$address!, Str :$comment --> Email::Address::Mailbox)
Constructs a new mailbox object from the given arguments. The address field is always required. If given as a string, it will be immediately parsed as an addr-spec. If this parse fails, an C<X::Email::Address> exception will be thrown.
When given, the comment must be a valid comment, meaning that every open parenthesis ("(") must have a matching closing parenthesis (")") and every closing parenthesis must have a matching opening parenthesis.
=head2 method display-name
has Str $.display-name is rw
This is the name associated with the mailbox address. In RFC 5322 and earlier RFCs this is the word or quoted words that come before the email address.
=head2 method address
has Email::Address::AddrSpec $.address is rw is required
This is the email address itself with the at-sign in it. In a mailbox address, this may be embedded inside of arrow-brackets.
=head2 method comment
has Email::Address::CommentStr $.comment is rw
This is the comment associated with this address. Comments may be inserted almost anywhere in the email address and are basically any string inside parenthesis. The only requirement is that any nested opening or closing parenthesis mark in the comment itself must have matching closing or opening parenthesis, respectively. When parsing all comments are concatenated together to form one long comment. When formatting, all the comments will be put together as a single comment added after the email address.
=head2 method local-part
method local-part(--> Str) is rw
This is a shortcut for getting at the C<.local-part> accessor of C<method address>.
=head2 method domain
method domain(--> Str) is rw
This is a shortcut for getting at the C<.domain> accessor of C<method address>.
=head2 method set-address
method set-address(Str $address, :$parser, :$actions --> Email::Address::AddrSpec)
This method will parse the given C<$address> and replace the C<method address>.
=head2 method guess-name
method guess-name(--> Str)
This ethod can be helpful for trying to get a name to associate with an email address. It will return the first of the following that are present in the email address:
=defn display-name
The display name given in the mailbox address is almost always the name of the owner or group for the name as that is the intended purpose of this field.
=defn comment
Sometimes the name for the email address will be in the comment.
=defn local-part
Failing the above, this is always present in a mailbox address and is the fallback for guessing the name.
=head2 method format
method format(--> Str)
method Str(--> Str)
method gist(--> Str)
Renders the email address in a standard form that follows RFC 5322 whenever possible.
=head1 VARIANTS
=head2 Email::Address::Mailbox::Parsed
When the parser is used to parse an email address, this object is returned, which also contains a reference to the original parsed string.
=head3 method original
has Str $.original
This is the original string that was parsed to create the mailbox address.
=head1 HELPERS
=head2 Email::Address::AddrSpec
The actual email address part of a mailbox is represented by an addr-spec. This contains the local-part and domain of the email address, which can be used for routing.
=head3 method local-part
has Str $.local-part is rw is required
This is the local part of an email address, often being the user name of the recipient. It is the part before at-sign.
=head3 method domain
has Str $.domain is rw is required
This is the domain part of an email address. This is generally a regular domain name address, but might also be an IP address or some other sort of name.
=head3 method format
method format(--> Str)
method Str(--> Str)
method gist(--> Str)
This will output the addr-spec in the RFC 5322 form or an obsolete form if necessary.
=head2 Email::Address::AddrSpec::Parsed
When an addr-spec is parsed, this class is returned, which also contains a reference back to the original parsed string.
=head3 method original
has Str $.original
This is the original string that was parsed to produce an addr-spec.
=end pod