Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add translated imap-codec Python examples
Browse files Browse the repository at this point in the history
This adds Python examples `parse_command`, `parse_greeting` and
`parse_response`, which are translated from the Rust examples available
for `imap-codec`.
HenningHolmDE committed Jul 12, 2024
1 parent 3574b4c commit e53223b
Showing 4 changed files with 204 additions and 0 deletions.
42 changes: 42 additions & 0 deletions bindings/imap-codec-python/examples/common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import sys
from enum import Enum
from typing import Optional

COLOR_SERVER = "\x1b[34m"
COLOR_CLIENT = "\x1b[31m"
RESET = "\x1b[0m"


class Role(Enum):
Client = 1
Server = 2


def read_more(buffer: bytearray, role: Role):
if not buffer:
prompt = "C: " if role == Role.Client else "S: "
else:
prompt = ".. "

line = read_line(prompt, role)

# If `read_line` returns `None`, standard input has been closed.
if line is None or line.strip() == "exit":
print("Exiting.")
sys.exit(0)

buffer += line.encode()


def read_line(prompt: str, role: Role) -> Optional[str]:
color = COLOR_CLIENT if role == Role.Client else COLOR_SERVER

try:
line = input(f"{prompt}{color}")
except EOFError:
# Standard input has been closed.
return None
finally:
print(RESET, end=None)

return line + "\r\n"
58 changes: 58 additions & 0 deletions bindings/imap-codec-python/examples/parse_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from common import COLOR_SERVER, RESET, Role, read_more
from imap_codec import (
CommandCodec,
CommandDecodeFailed,
CommandDecodeIncomplete,
CommandDecodeLiteralFound,
)

WELCOME = r"""# Parsing of IMAP commands
"C:" denotes the client,
"S:" denotes the server, and
".." denotes the continuation of an (incomplete) command, e.g., due to the use of an IMAP literal.
Note: "\n" will be automatically replaced by "\r\n".
--------------------------------------------------------------------------------------------------
Enter IMAP command (or "exit").
"""


def main():
print(WELCOME)

buffer = bytearray()

while True:
# Try to parse the first command in `buffer`.
try:
remaining, command = CommandCodec.decode(bytes(buffer))
# Parser succeeded.
# Do something with the command ...
print(command)
# ... and proceed with the remaining data.
buffer = bytearray(remaining)
except CommandDecodeIncomplete:
# Parser needs more data.
# Read more data.
read_more(buffer, Role.Client)
except CommandDecodeLiteralFound:
# Parser needs more data, and a command continuation request is expected.
# Simulate literal acknowledgement ...
print(f"S: {COLOR_SERVER}+ {RESET}")

# ... and read more data.
read_more(buffer, Role.Client)
except CommandDecodeFailed:
# Parser failed.
print("Error parsing command.")
print("Clearing buffer.")

# Clear the buffer and proceed with loop.
buffer.clear()


if __name__ == "__main__":
main()
44 changes: 44 additions & 0 deletions bindings/imap-codec-python/examples/parse_greeting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from common import Role, read_more
from imap_codec import GreetingCodec, GreetingDecodeFailed, GreetingDecodeIncomplete

WELCOME = r"""# Parsing of IMAP greetings
"S:" denotes the server.
Note: "\n" will be automatically replaced by "\r\n".
--------------------------------------------------------------------------------------------------
Enter IMAP greeting (or "exit").
"""


def main():
print(WELCOME)

buffer = bytearray()

while True:
# Try to parse the first greeting in `buffer`.
try:
remaining, greeting = GreetingCodec.decode(bytes(buffer))
# Parser succeeded.
# Do something with the greeting ...
print(greeting)
# ... and proceed with the remaining data.
buffer = bytearray(remaining)
except GreetingDecodeIncomplete:
# Parser needs more data.
# Read more data.
read_more(buffer, Role.Server)
except GreetingDecodeFailed:
# Parser failed.
print("Error parsing greeting.")
print("Clearing buffer.")

# Clear the buffer and proceed with loop.
buffer.clear()


if __name__ == "__main__":
main()
60 changes: 60 additions & 0 deletions bindings/imap-codec-python/examples/parse_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from common import Role, read_more
from imap_codec import (
ResponseCodec,
ResponseDecodeFailed,
ResponseDecodeIncomplete,
ResponseDecodeLiteralFound,
)

WELCOME = r"""# Parsing of IMAP responses
"S:" denotes the server, and
".." denotes the continuation of an (incomplete) response, e.g., due to the use of an IMAP literal.
Note: "\n" will be automatically replaced by "\r\n".
--------------------------------------------------------------------------------------------------
Enter IMAP response (or "exit").
"""


def main():
print(WELCOME)

buffer = bytearray()

while True:
# Try to parse the first response in `buffer`.
try:
remaining, response = ResponseCodec.decode(bytes(buffer))
# Parser succeeded.
# Do something with the response ...
print(response)
# ... and proceed with the remaining data.
buffer = bytearray(remaining)
except ResponseDecodeIncomplete:
# Parser needs more data.
# Read more data.
read_more(buffer, Role.Server)
except ResponseDecodeLiteralFound:
# Parser needs more data.
#
# A client MUST receive any literal and can't reject it. However, if the literal is too
# large, the client would have the (semi-optimal) option to still *read it* but discard
# the data chunk by chunk. It could also close the connection. This is why we have this
# option.
#
# Read more data.
read_more(buffer, Role.Server)
except ResponseDecodeFailed:
# Parser failed.
print("Error parsing response.")
print("Clearing buffer.")

# Clear the buffer and proceed with loop.
buffer.clear()


if __name__ == "__main__":
main()

0 comments on commit e53223b

Please sign in to comment.