Skip to content

Commit a6b0c3a

Browse files
authored
Update with better support for quoted strings in the local portion of a mailbox address (#1)
1 parent 488d1c0 commit a6b0c3a

File tree

12 files changed

+329
-121
lines changed

12 files changed

+329
-121
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
generated
3-
dist
3+
dist
4+
.vscode

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
## Next
44

5+
## 0.0.2 (2025-11-20)
6+
7+
- Improve support for quoted strings in the local portion of an email
8+
- Export the `mailbox` function by default
9+
- Return a discriminated union instead of a success object or array of error strings
10+
- Automate generating the example code used in the README
11+
- Add explicit license
12+
513
## 0.0.1 (2025-09-23)
614

715
- Document how it's used and why there won't be many updates

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Spencer Van Wessem Scorcelletti
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 131 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,75 +11,159 @@ Also:
1111

1212
⚠️ THIS LIBRARY IS NOT ABANDONED ⚠️
1313

14-
While there are few commits and not much activity, sometimes things are just done and work.
14+
At some point in the future it may seem like this project is abandoned, but it's not. While there _may_ be few commits or not much activity, the code from this project is almost entirely derived from the email [rfc](https://www.rfc-editor.org/rfc/rfc5322#section-3.4) spec, and it therefore benefits from all the very mature work that has gone into those efforts.
1515

1616
Examples:
1717

1818
```ts
19-
import { mailbox } from './src'
20-
21-
/**
22-
* { local: 'bob', domain: 'example.com' }
23-
*/
24-
mailbox('bob@example.com')
25-
26-
/**
27-
* { name: undefined, local: 'bob', domain: 'example.com', addr: 'bob@example.com' }
28-
*/
29-
mailbox('<bob@example.com>')
30-
31-
/**
32-
{
33-
name: 'Bruce Springsteen',
34-
local: 'bruce',
35-
domain: 'springsteen.com',
36-
addr: 'bruce@springsteen.com'
37-
}
38-
*/
39-
mailbox('Bruce Springsteen <bruce@springsteen.com>')
40-
41-
42-
/**
43-
{
44-
name: 'Bruce "The Boss" Springsteen',
45-
local: 'bruce',
46-
domain: 'springsteen.com',
47-
addr: 'bruce@springsteen.com'
48-
}
49-
*/
50-
mailbox('Bruce "The Boss" Springsteen <bruce@springsteen.com>')
19+
import mailbox from 'typescript-mailbox-parser'
20+
21+
// {
22+
// "ok": true,
23+
// "local": "bob",
24+
// "domain": "example.com",
25+
// "addr": "bob@example.com"
26+
// }
27+
console.log(mailbox('<bob@example.com>'))
28+
29+
// {
30+
// "ok": true,
31+
// "name": "Bob Hope",
32+
// "local": "bob",
33+
// "domain": "example.com",
34+
// "addr": "bob@example.com"
35+
// }
36+
console.log(mailbox('Bob Hope <bob@example.com>'))
37+
38+
// {
39+
// "ok": true,
40+
// "name": "Bob Hope",
41+
// "local": "bob",
42+
// "domain": "example.com",
43+
// "addr": "bob@example.com"
44+
// }
45+
console.log(mailbox('"Bob Hope" <bob@example.com>'))
46+
47+
// {
48+
// "ok": true,
49+
// "name": "Bruce \"The Boss\" Springsteen",
50+
// "local": "bruce",
51+
// "domain": "example.com",
52+
// "addr": "bruce@example.com"
53+
// }
54+
console.log(mailbox('Bruce "The Boss" Springsteen <bruce@example.com>'))
55+
56+
// {
57+
// "ok": true,
58+
// "local": "bob",
59+
// "domain": "example",
60+
// "addr": "bob@example"
61+
// }
62+
console.log(mailbox('bob@example'))
63+
64+
// {
65+
// "ok": true,
66+
// "local": "BOB",
67+
// "domain": "example",
68+
// "addr": "BOB@example"
69+
// }
70+
console.log(mailbox('BOB@example'))
71+
72+
// {
73+
// "ok": true,
74+
// "local": "bob",
75+
// "domain": "EXAMPLE",
76+
// "addr": "bob@EXAMPLE"
77+
// }
78+
console.log(mailbox('bob@EXAMPLE'))
79+
80+
// {
81+
// "ok": true,
82+
// "local": "a.b.c",
83+
// "domain": "d.e.f.g",
84+
// "addr": "a.b.c@d.e.f.g"
85+
// }
86+
console.log(mailbox('a.b.c@d.e.f.g'))
87+
88+
// {
89+
// "ok": true,
90+
// "local": "\"site.local.test:1111\"",
91+
// "domain": "example.com",
92+
// "addr": "\"site.local.test:1111\"@example.com"
93+
// }
94+
console.log(mailbox('"site.local.test:1111"@example.com'))
95+
96+
// {
97+
// "ok": true,
98+
// "local": "\"hello, world\"",
99+
// "domain": "example.com",
100+
// "addr": "\"hello, world\"@example.com"
101+
// }
102+
console.log(mailbox('"hello, world"@example.com'))
103+
104+
// {
105+
// "ok": false,
106+
// "errors": [
107+
// "{\"pos\":{\"overallPos\":11,\"line\":1,\"offset\":11},\"expmatches\":[{\"kind\":\"RegexMatch\",\"literal\":\"[A-Za-z0-9!#$%&\\\\x27\\\\*\\\\+\\\\-\\\\/=?^_\\\\`{|}~]\",\"negated\":false},{\"kind\":\"RegexMatch\",\"literal\":\"\\\\x20\",\"negated\":false},{\"kind\":\"RegexMatch\",\"literal\":\"\\\\x09\",\"negated\":false},{\"kind\":\"RegexMatch\",\"literal\":\"\\\\r\\\\n\",\"negated\":false},{\"kind\":\"RegexMatch\",\"literal\":\"\\\\(\",\"negated\":false},{\"kind\":\"RegexMatch\",\"literal\":\"\\\\x22\",\"negated\":false},{\"kind\":\"RegexMatch\",\"literal\":\"<\",\"negated\":false}]}"
108+
// ]
109+
// }
110+
console.log(mailbox('foo bar baz'))
51111
```
52112

53113
## Installation and Usage
54114

55115
To install `npm install typescript-mailbox-parser`
56116

57-
Then to use it you just need to import it and call the `mailbox` function on a string. If successful it will return an object representing the parts of the email address (mailbox).
117+
Then to use it you just need to import it and call the `mailbox` function on a string.
58118

59119
```ts
60-
import { mailbox } from 'typescript-mailbox-parser'
120+
import mailbox from 'typescript-mailbox-parser'
61121

62122
const email = mailbox('hello@example.com')
63123
```
64124

65-
If the string supplied is not a valid email address (mailbox), then it will return an array of error strings.
125+
It returns the following type
66126

67127
```ts
68-
import { mailbox } from 'typescript-mailbox-parser'
128+
type MailboxParseResult =
129+
| { ok: false; errors: string[] }
130+
| { ok: true; addr: string; name?: string; local: string; domain: string }
131+
```
132+
133+
In other words, if successful it will return an object representing the parts of the email address (mailbox):
134+
135+
```ts
136+
import mailbox from 'typescript-mailbox-parser'
137+
138+
// {
139+
// ok: true,
140+
// name: 'Bruce "The Boss" Springsteen',
141+
// local: 'bruce',
142+
// domain: 'example.com',
143+
// addr: 'bruce@example.com'
144+
// }
145+
console.log(mailbox('Bruce "The Boss" Springsteen <bruce@example.com>'))
146+
```
147+
148+
If unsuccessful it will return an object with an `errors` field containing an array of error messages as strings:
69149

70-
/**
71-
[
72-
'{"pos":{"overallPos":11,"line":1,"offset":11},"expmatches":[{"kind":"RegexMatch","literal":"[A-Za-z0-9!#$%&\\\\x27\\\\*\\\\+\\\\-\\\\/=?^_\\\\`{|}~]","negated":false},{"kind":"RegexMatch","literal":"\\\\x20","negated":false},{"kind":"RegexMatch","literal":"\\\\x09","negated":false},{"kind":"RegexMatch","literal":"\\\\r\\\\n","negated":false},{"kind":"RegexMatch","literal":"\\\\(","negated":false},{"kind":"RegexMatch","literal":"\\\\x22","negated":false},{"kind":"RegexMatch","literal":"<","negated":false}]}'
73-
]
74-
*/
150+
```ts
151+
import mailbox from 'typescript-mailbox-parser'
152+
153+
// {
154+
// ok: false,
155+
// errors: [
156+
// '{"pos":{"overallPos":11,"line":1,"offset":11},"expmatches":[{"kind":"RegexMatch","literal":"[A-Za-z0-9!#$%&\\\\x27\\\\*\\\\+\\\\-\\\\/=?^_\\\\`{|}~]","negated":false},{"kind":"RegexMatch","literal":"\\\\x20","negated":false},{"kind":"RegexMatch","literal":"\\\\x09","negated":false},{"kind":"RegexMatch","literal":"\\\\r\\\\n","negated":false},{"kind":"RegexMatch","literal":"\\\\(","negated":false},{"kind":"RegexMatch","literal":"\\\\x22","negated":false},{"kind":"RegexMatch","literal":"<","negated":false}]}'
157+
// ]
158+
// }
75159
const email = mailbox('foo bar baz')
76160
```
77161

78162
For development
79163

80164
```sh
81165
# run tests
82-
npm run test
166+
npm test
83167

84168
# build the parser from the grammar
85169
npm run build:parser
@@ -90,10 +174,13 @@ npm run build:compile
90174
# combine build:parser and build:compile
91175
npm run build:all
92176

177+
# generate the examples used in the README
178+
npm run docs:examples
179+
93180
# run formatter
94181
npm run fmt
95182
```
96183

97184
## Notes
98185

99-
This _should_ in theory work with 100% accuracy, as the logic is based off the [rfc5322](https://www.rfc-editor.org/rfc/rfc5322) specification... however mistakes can always be made. Please file an issue if there are any bugs.
186+
This _should_ in theory work with 100% accuracy, as the logic is based off the [rfc5322](https://www.rfc-editor.org/rfc/rfc5322) specification. Please file an issue if there are any bugs.

example.ts

Lines changed: 0 additions & 43 deletions
This file was deleted.

examples.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import mailbox from './dist'
2+
import vm from 'node:vm'
3+
4+
const sandbox = { mailbox, console }
5+
vm.createContext(sandbox)
6+
7+
const examples = [
8+
`mailbox('<bob@example.com>')`,
9+
`mailbox('Bob Hope <bob@example.com>')`,
10+
`mailbox('"Bob Hope" <bob@example.com>')`,
11+
`mailbox('Bruce "The Boss" Springsteen <bruce@example.com>')`,
12+
`mailbox('bob@example')`,
13+
`mailbox('BOB@example')`,
14+
`mailbox('bob@EXAMPLE')`,
15+
`mailbox('a.b.c@d.e.f.g')`,
16+
`mailbox('"site.local.test:1111"@example.com')`,
17+
`mailbox('"hello, world"@example.com')`,
18+
`mailbox('foo bar baz')`,
19+
]
20+
21+
for (const e of examples) {
22+
const result = vm.runInContext(e, sandbox)
23+
// Print the result as a commented block
24+
const commented = JSON.stringify(result, null, 2)
25+
.split('\n')
26+
.map((line) => `// ${line}`)
27+
.join('\n')
28+
29+
console.log(commented)
30+
console.log(`console.log(${e})\n`)
31+
}

0 commit comments

Comments
 (0)