aboutsummaryrefslogtreecommitdiff
path: root/ctec/imap_response.py
blob: cad76c96ee6471af803340a35089f055eebafabd (plain)
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
# this should really be in the stdlib imo
from dataclasses import dataclass
from typing import List as ListT, Tuple, Optional

from .parse_utils import (
    Parser, take_while1, tag, delimited, take_n, alt, separated_many0, separated_triple, all_consuming, verify,
    preceded, and_then, itag, take_while0, as_predicate
)

__all__ = [
    'List',
]

# level 0
ctl: Parser[str] = verify(take_n(1), lambda c: not c.isprintable())

digit: Parser[str] = verify(take_n(1), lambda c: c in '0123456789')

dquote: Parser[str] = tag('"')

list_wildcards: Parser[str] = alt(tag('%'), tag('*'))

nil: Parser[str] = itag('NIL')

resp_specials: Parser[str] = tag(']')

text_char: Parser[str] = verify(take_n(1), lambda c: c not in '\r\n')

# level 1
number: Parser[str] = take_while1(as_predicate(digit))

quoted_specials: Parser[str] = alt(dquote, tag('\\'))

# level 2
atom_specials: Parser[str] = alt(verify(take_n(1), lambda c: c in '(){ '), ctl, list_wildcards, quoted_specials, resp_specials)

literal: Parser[str] = and_then(
    delimited(tag('{'), number, tag('}\r\n')),
    lambda n: preceded(delimited(tag('{'), number, tag('}\r\n')), take_n(int(n)))
)

quoted_char: Parser[str] = alt(verify(text_char, lambda c: quoted_specials(c) is None), preceded(tag('\\'), quoted_specials))

# level 3
atom_char: Parser[str] = verify(take_n(1), lambda c: atom_specials(c) is None)

quoted: Parser[str] = delimited(dquote, take_while0(as_predicate(quoted_char)), dquote)

# level 4
astring_char: Parser[str] = alt(atom_char, resp_specials)

atom: Parser[str] = take_while1(as_predicate(atom_char))

string: Parser[str] = alt(quoted, literal)

# level 5
astring: Parser[str] = alt(take_while1(as_predicate(astring_char)), string)

flag_extension: Parser[str] = preceded(tag('\\'), atom)

# level 6
mailbox: Parser[str] = alt(itag('INBOX'), astring)

mbx_list_flag: Parser[str] = alt(itag(r'\Noselect'), itag(r'\Marked'), itag(r'\Unmarked'), itag(r'\Noinferiors'), flag_extension)

# level 7
mbx_list_flags: Parser[ListT[str]] = separated_many0(mbx_list_flag, tag(' '))

# level 8
mailbox_list: Parser[Tuple[ListT[str], Optional[str], str]] = separated_triple(
    delimited(tag('('), mbx_list_flags, tag(')')),
    tag(' '),
    alt(delimited(dquote, quoted_char, dquote), nil),
    tag(' '),
    mailbox,
)

@dataclass
class List:
    attributes: ListT[str]
    delimiter: Optional[str]
    name: str

    @staticmethod
    def parse(response_bytes: bytes) -> 'List':
        response = response_bytes.decode('ASCII')
        parser = all_consuming(mailbox_list, debug=True)
        parse_result = parser(response)
        if parse_result is None:
            raise ValueError('invalid List.parse argument {}', repr(response))
        (attributes, delimiter, name), _ = parse_result
        return List(attributes, delimiter, name)