Skip to content

Commit 488d1c0

Browse files
committed
Add rfc5322 grammar
Manually tree shake rfc5322 grammar until left with mailbox grammar Stash commit, while debugging which whitespace to preserve Finish getting all the whitespace and quoted characters right Fix broken rule in other grammars Exclude generated dir from repo and move grammars that serve more as documentation to a separate grammars dir Add a grammar, compile, and overarching build step + some dev deps Run formatter Add README + examples Add changelog Add tests Run tests on actual packed library
0 parents  commit 488d1c0

15 files changed

+6003
-0
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock.json -diff

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
generated
3+
dist

.prettierrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all",
4+
"semi": false,
5+
"tabWidth": 2,
6+
"printWidth": 80
7+
}

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Changelog
2+
3+
## Next
4+
5+
## 0.0.1 (2025-09-23)
6+
7+
- Document how it's used and why there won't be many updates
8+
- Write a wrapper around the mailbox parse result to create an API
9+
- Tree shake the RFC 5322 grammar to isolate just the mailbox part

README.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# typescript-mailbox-parser
2+
3+
Parse an email address (i.e. [mailbox](https://www.rfc-editor.org/rfc/rfc5322#section-3.4)) into it main parts: display name, local portion, domain.
4+
5+
Also:
6+
7+
* it has 0 dependencies
8+
* is 100% based off [the spec](https://www.rfc-editor.org/rfc/rfc5322#section-3.4)
9+
* is compatible in browser or node (maybe Deno too?)
10+
* Preserves case sensitivity
11+
12+
⚠️ THIS LIBRARY IS NOT ABANDONED ⚠️
13+
14+
While there are few commits and not much activity, sometimes things are just done and work.
15+
16+
Examples:
17+
18+
```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>')
51+
```
52+
53+
## Installation and Usage
54+
55+
To install `npm install typescript-mailbox-parser`
56+
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).
58+
59+
```ts
60+
import { mailbox } from 'typescript-mailbox-parser'
61+
62+
const email = mailbox('hello@example.com')
63+
```
64+
65+
If the string supplied is not a valid email address (mailbox), then it will return an array of error strings.
66+
67+
```ts
68+
import { mailbox } from 'typescript-mailbox-parser'
69+
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+
*/
75+
const email = mailbox('foo bar baz')
76+
```
77+
78+
For development
79+
80+
```sh
81+
# run tests
82+
npm run test
83+
84+
# build the parser from the grammar
85+
npm run build:parser
86+
87+
# compile the parser and output to dist
88+
npm run build:compile
89+
90+
# combine build:parser and build:compile
91+
npm run build:all
92+
93+
# run formatter
94+
npm run fmt
95+
```
96+
97+
## Notes
98+
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.

example.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { mailbox } from './src'
2+
3+
/**
4+
* { local: 'bob', domain: 'example.com', addr: 'bob@example.com' }
5+
*/
6+
console.log(mailbox('bob@example.com'))
7+
8+
/**
9+
{
10+
name: undefined,
11+
local: 'bob',
12+
domain: 'example.com',
13+
addr: 'bob@example.com'
14+
}
15+
*/
16+
console.log(mailbox('<bob@example.com>'))
17+
18+
/**
19+
{
20+
name: 'Bruce Springsteen',
21+
local: 'bruce',
22+
domain: 'springsteen.com',
23+
addr: 'bruce@springsteen.com'
24+
}
25+
*/
26+
console.log(mailbox('Bruce Springsteen <bruce@springsteen.com>'))
27+
28+
/**
29+
{
30+
name: 'Bruce "The Boss" Springsteen',
31+
local: 'bruce',
32+
domain: 'springsteen.com',
33+
addr: 'bruce@springsteen.com'
34+
}
35+
*/
36+
console.log(mailbox('Bruce "The Boss" Springsteen <bruce@springsteen.com>'))
37+
38+
/**
39+
[
40+
'{"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}]}'
41+
]
42+
*/
43+
console.log(mailbox('foo bar baz'))

grammars/mailbox.peg

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Mailbox grammar
2+
// This is the [rfc5322](./rfc5322.peg) grammar treeshaken to exclude everything but mailbox
3+
// See [mailbox-computed](./mailbox-computed.peg) for the actual grammar used with the lib
4+
5+
mailbox := name_addr | addr_spec
6+
name_addr := display_name? angle_addr
7+
addr_spec := local_part '@' domain
8+
display_name := phrase
9+
angle_addr := CFWS? '<' addr_spec '>' CFWS? | obs_angle_addr
10+
local_part := dot_atom | quoted_string | obs_local_part
11+
domain := dot_atom | domain_literal | obs_domain
12+
phrase := word+ | obs_phrase
13+
CFWS := { FWS? comment }+ FWS? | FWS
14+
obs_angle_addr := CFWS? '<' obs_route addr_spec '>' CFWS?
15+
dot_atom := CFWS? dot_atom_text CFWS?
16+
quoted_string := CFWS? DQUOTE { FWS? qcontent }* FWS? DQUOTE CFWS?
17+
obs_local_part := word { '\.' word }*
18+
domain_literal := CFWS? '\[' { FWS? dtext }* FWS? '\]' CFWS?
19+
obs_domain := atom { '\.' atom }*
20+
word := atom | quoted_string
21+
obs_phrase := word { word | '.' | CFWS }*
22+
FWS := { WSP* CRLF }? WSP+ | obs_FWS
23+
comment := '\(' { FWS? ccontent }* FWS? '\)'
24+
obs_route := obs_domain_list ':'
25+
dot_atom_text := atext+ { '\.' atext+ }*
26+
DQUOTE := '\x22' // double quote, i.e. '"'
27+
qcontent := qtext | quoted_pair
28+
dtext := '[\x21-\x5a]' | '[\x5e-\x7e]' | obs_dtext
29+
atom := CFWS? atext+ CFWS?
30+
WSP := SP | HTAB // white space
31+
obs_FWS := WSP+ { CRLF WSP+ }*
32+
CRLF := '\r\n' // Internet standard newline
33+
ccontent := ctext | quoted_pair | comment
34+
obs_domain_list := { CFWS | ',' }* '@' domain { ',' CFWS? { '@' domain }? }*
35+
atext := '[A-Za-z0-9!#$%&\x27\*\+\-\/=?^_`{|}~]'
36+
qtext := '\x21' | '[\x23-\x5b]' | '[\x5d-\x7e]' | obs_qtext
37+
quoted_pair := '\\' { VCHAR | WSP } | obs_qp
38+
obs_dtext := obs_NO_WS_CTL | quoted_pair
39+
SP := '\x20' // space, i.e. 'Space'
40+
HTAB := '\x09' // horizontal tab, i.e. 'TAB'
41+
ctext := '[\x21-\x27]' | '[\x2a-\x5b]' | '[\x5d-\x7e]' | obs_ctext
42+
obs_qtext := obs_NO_WS_CTL
43+
obs_qp := '\\' { '\x00' | obs_NO_WS_CTL | LF | CR }
44+
VCHAR := '[\x21-\x7E]' // visible (printing) characters
45+
obs_NO_WS_CTL := '[\x01-\x08]' | '\x0B' | '\x0C' | '[\x0E-\x1F]' | '\x7F'
46+
obs_ctext := obs_NO_WS_CTL
47+
LF := '\x0A' // linefeed, i.e. '\n'
48+
CR := '\x0D' // carriage return, i.e. '\r'

0 commit comments

Comments
 (0)