Skip to content

Commit

Permalink
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`.
  • Loading branch information
HenningHolmDE committed Jul 13, 2024
1 parent 5b60b23 commit ec57ca7
Show file tree
Hide file tree
Showing 4 changed files with 194 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"
53 changes: 53 additions & 0 deletions bindings/imap-codec-python/examples/parse_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from common import COLOR_SERVER, RESET, Role, read_more
from imap_codec import CommandCodec, DecodeFailed, DecodeIncomplete, DecodeLiteralFound

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 DecodeIncomplete:
# Parser needs more data.
# Read more data.
read_more(buffer, Role.Client)
except DecodeLiteralFound:
# 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 DecodeFailed:
# 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 DecodeFailed, DecodeIncomplete, GreetingCodec

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 DecodeIncomplete:
# Parser needs more data.
# Read more data.
read_more(buffer, Role.Server)
except DecodeFailed:
# Parser failed.
print("Error parsing greeting.")
print("Clearing buffer.")

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


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

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 DecodeIncomplete:
# Parser needs more data.
# Read more data.
read_more(buffer, Role.Server)
except DecodeLiteralFound:
# 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 DecodeFailed:
# 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 ec57ca7

Please sign in to comment.