aboutsummaryrefslogtreecommitdiff
path: root/ctec/imap_response.py
diff options
context:
space:
mode:
Diffstat (limited to 'ctec/imap_response.py')
-rw-r--r--ctec/imap_response.py92
1 files changed, 69 insertions, 23 deletions
diff --git a/ctec/imap_response.py b/ctec/imap_response.py
index 23ca943..cad76c9 100644
--- a/ctec/imap_response.py
+++ b/ctec/imap_response.py
@@ -1,46 +1,92 @@
# this should really be in the stdlib imo
from dataclasses import dataclass
-from typing import List as ListT, Union, ClassVar
+from typing import List as ListT, Tuple, Optional
-from .parse_utils import ParseResult, Parser, take_while1, tag, delimited, take_n, alt, map_parser, separated_many0, separated_triple, all_consuming
+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',
]
-atom: Parser[str] = take_while1(lambda c: c.isalnum() or c in r'\/')
+# level 0
+ctl: Parser[str] = verify(take_n(1), lambda c: not c.isprintable())
-number: Parser[int] = map_parser(take_while1(str.isnumeric), int)
+digit: Parser[str] = verify(take_n(1), lambda c: c in '0123456789')
-def literal_string(text: str) -> ParseResult[str]:
- delimited_result = delimited(tag('{'), number, tag('}\r\n'))(text)
- if delimited_result is None:
- return None
- count, text = delimited_result
- return take_n(count)(text)
+dquote: Parser[str] = tag('"')
-quoted_string: Parser[str] = delimited(tag('"'), take_while1(lambda c: c not in '\r\n"'), tag('"'))
+list_wildcards: Parser[str] = alt(tag('%'), tag('*'))
-string: Parser[str] = alt(literal_string, quoted_string)
+nil: Parser[str] = itag('NIL')
-astring: Parser[str] = alt(atom, string)
+resp_specials: Parser[str] = tag(']')
-data_item: Parser[Union[int, str]] = alt(number, atom, string)
+text_char: Parser[str] = verify(take_n(1), lambda c: c not in '\r\n')
-ParensList = ListT[Union[int, str, 'ParensList']]
-def parens_list(text: str) -> ParseResult[ParensList]:
- return delimited(tag('('), separated_many0(alt(data_item, parens_list), tag(' ')), tag(')'))(text)
+# 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: str
+ delimiter: Optional[str]
name: str
@staticmethod
- def parse(response: bytes) -> 'List':
- response = response.decode('ASCII')
- print(response)
- parser = all_consuming(separated_triple(parens_list, tag(' '), string, tag(' '), astring), debug=True)
- (attributes, delimiter, name), _ = parser(response)
+ 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)