Module mudproto.mpi
¶
Mume Remote Editing Protocol.
Class MPIProtocol(ConnectionInterface)
¶
Implements support for the Mume remote editing protocol.
Source code in mudproto/mpi.py
class MPIProtocol(ConnectionInterface):
"""Implements support for the Mume remote editing protocol."""
def __init__(self, *args: Any, output_format: str, **kwargs: Any) -> None:
"""
Defines the constructor.
Args:
*args: Positional arguments to be passed to the parent constructor.
output_format: The output format to be used.
**kwargs: Key-word only arguments to be passed to the parent constructor.
Raises:
ValueError: Editor or pager not found.
"""
self.output_format: str = output_format
super().__init__(*args, **kwargs)
self.state: MPIState = MPIState.DATA
"""The state of the state machine."""
self._mpi_buffer: bytearray = bytearray()
self._mpi_threads: list[threading.Thread] = []
self.command_map: MPICommandMapType = {
b"E": self.edit,
b"V": self.view,
}
"""A mapping of bytes to callables."""
editors: dict[str, str] = {
"win32": "notepad.exe",
}
pagers: dict[str, str] = {
"win32": "notepad.exe",
}
default_editor: str = editors.get(sys.platform, "nano")
default_pager: str = pagers.get(sys.platform, "less")
editor: str | None = shutil.which(os.getenv("VISUAL", "") or os.getenv("EDITOR", default_editor))
pager: str | None = shutil.which(os.getenv("PAGER", default_pager))
self._is_word_wrapping: bool = False
if editor is None: # pragma: no cover
raise ValueError("MPI editor executable not found.")
if pager is None: # pragma: no cover
raise ValueError("MPI pager executable not found.")
self.editor: str = editor
"""The program to use for editing received text."""
self.pager: str = pager
"""The program to use for viewing received read-only text."""
@property
def is_word_wrapping(self) -> bool:
"""Specifies whether text should be word wrapped during editing or not."""
return self._is_word_wrapping
@is_word_wrapping.setter
def is_word_wrapping(self, value: bool) -> None:
self._is_word_wrapping = value
def edit(self, data: bytes) -> None:
"""
Edits text using the program defined in `editor`.
Args:
data: Received data from Mume, containing the session, description, and body of the text.
"""
# Use windows line endings when editing the file.
newline: str = "\r\n"
# The MUME server sends the MPI data encoded in Latin-1.
session, _, body = str(data, "latin-1")[1:].split("\n", 2)
with tempfile.NamedTemporaryFile(
"w", encoding="utf-8", newline=newline, prefix="mume_editing_", suffix=".txt", delete=False
) as temp_file_obj:
file_path = Path(temp_file_obj.name)
temp_file_obj.write(body)
last_modified = file_path.stat().st_mtime
if self.output_format == "tintin":
print(f"MPICOMMAND:{self.editor} {file_path}:MPICOMMAND")
input("Continue:")
else:
subprocess.run((*self.editor.split(), str(file_path))) # NOQA: PLW1510, S603
response: str
if file_path.stat().st_mtime == last_modified:
# The user closed the text editor without saving. Cancel the editing session.
response = f"C{session}\n"
else:
if self.is_word_wrapping:
with file_path.open(encoding="utf-8", newline=newline) as file_obj:
text: str = file_obj.read()
text = self.postprocess(text)
with file_path.open("w", encoding="utf-8", newline=newline) as file_obj:
file_obj.write(text)
with file_path.open(encoding="utf-8", newline=newline) as file_obj:
response = f"E{session}\n{file_obj.read().strip()}\n"
file_path.unlink(missing_ok=True)
# MUME requires that output body be encoded in Latin-1 with Unix line endings.
output: bytes = bytes(response, "latin-1").replace(CR, b"")
self.write(MPI_INIT + b"E" + b"%d" % len(output) + LF + output)
def view(self, data: bytes) -> None:
"""
Views text using the program defined in `pager`.
Args:
data: Received data from Mume, containing the text.
"""
# Use windows line endings when viewing the file.
newline: str = "\r\n"
# The MUME server sends the MPI data encoded in Latin-1.
body: str = str(data, "latin-1")
with tempfile.NamedTemporaryFile(
"w", encoding="utf-8", newline=newline, prefix="mume_viewing_", suffix=".txt", delete=False
) as file_obj:
file_path = Path(file_obj.name)
file_obj.write(body)
if self.output_format == "tintin":
print(f"MPICOMMAND:{self.pager} {file_path}:MPICOMMAND")
else:
subprocess.run((*self.pager.split(), str(file_path))) # NOQA: PLW1510, S603
file_path.unlink(missing_ok=True)
def on_data_received(self, data: bytes) -> None: # NOQA: C901, D102, PLR0912, PLR0915
app_data_buffer: bytearray = bytearray()
while data:
if self.state is MPIState.DATA:
app_data, separator, data = data.partition(LF)
app_data_buffer.extend(app_data + separator)
if separator:
self.state = MPIState.NEWLINE
elif self.state is MPIState.NEWLINE:
if MPI_INIT.startswith(data[: len(MPI_INIT)]):
# Data starts with some or all of the MPI_INIT sequence.
self.state = MPIState.INIT
else:
self.state = MPIState.DATA
elif self.state is MPIState.INIT:
remaining = len(MPI_INIT) - len(self._mpi_buffer)
self._mpi_buffer.extend(data[:remaining])
data = data[remaining:]
if self._mpi_buffer == MPI_INIT:
# The final byte in the MPI_INIT sequence has been reached.
if app_data_buffer:
super().on_data_received(bytes(app_data_buffer))
app_data_buffer.clear()
self._mpi_buffer.clear()
self.state = MPIState.COMMAND
elif not MPI_INIT.startswith(self._mpi_buffer):
# The Bytes in the buffer are not part of an MPI init sequence.
data = bytes(self._mpi_buffer) + data
self._mpi_buffer.clear()
self.state = MPIState.DATA
elif self.state is MPIState.COMMAND:
# The MPI command is a single byte.
self._command, data = data[:1], data[1:]
self.state = MPIState.LENGTH
elif self.state is MPIState.LENGTH:
length, separator, data = data.partition(LF)
self._mpi_buffer.extend(length)
if not self._mpi_buffer.isdigit():
logger.warning(f"Invalid data {self._mpi_buffer!r} in MPI length. Digit expected.")
data = MPI_INIT + self._command + bytes(self._mpi_buffer) + separator + data
del self._command
self._mpi_buffer.clear()
self.state = MPIState.DATA
elif separator:
# The buffer contains the length of subsequent bytes to be received.
self._length = int(self._mpi_buffer)
self._mpi_buffer.clear()
self.state = MPIState.BODY
elif self.state is MPIState.BODY:
remaining = self._length - len(self._mpi_buffer)
self._mpi_buffer.extend(data[:remaining])
data = data[remaining:]
if len(self._mpi_buffer) == self._length:
# The final byte in the expected MPI data has been received.
self.on_command(self._command, bytes(self._mpi_buffer))
del self._command
del self._length
self._mpi_buffer.clear()
self.state = MPIState.DATA
if app_data_buffer:
super().on_data_received(bytes(app_data_buffer))
def on_command(self, command: bytes, data: bytes) -> None:
"""
Called when an MPI command is received.
Args:
command: The MPI command, consisting of a single byte.
data: The payload.
"""
if command not in self.command_map:
logger.warning(f"Invalid MPI command {command!r}.")
self.on_unhandled_command(command, data)
elif self.command_map[command] is not None:
thread = threading.Thread(target=self.command_map[command], args=(data,), daemon=True)
self._mpi_threads.append(thread)
thread.start()
def on_connection_made(self) -> None: # NOQA: D102
# Identify for Mume Remote Editing.
self.write(MPI_INIT + b"I" + LF)
def on_connection_lost(self) -> None: # NOQA: D102
# Clean up any active editing sessions.
for thread in self._mpi_threads:
thread.join()
self._mpi_threads.clear()
def on_unhandled_command(self, command: bytes, data: bytes) -> None:
"""
Called for commands for which no handler is installed.
Args:
command: The MPI command, consisting of a single byte.
data: The payload.
"""
super().on_data_received(MPI_INIT + command + b"%d" % len(data) + LF + data)
def postprocess(self, text: str) -> str:
"""
Reformats text before it is sent to the game when wordwrapping is enabled.
Args:
text: The text to be processed.
Returns:
The text with formatting applied.
"""
paragraphs: list[str] = self.get_paragraphs(text)
for i, paragraph in enumerate(paragraphs):
if not self.is_comment(paragraph):
paragraphs[i] = self.word_wrap(self.capitalise(self.collapse_spaces(paragraph)))
return "\n".join(paragraphs)
def get_paragraphs(self, text: str) -> list[str]:
"""
Extracts paragraphs from a string.
Args:
text: The text to analyze.
Returns:
The extracted paragraphs.
"""
lines: list[str] = text.splitlines()
lineno: int = 0
while lineno < len(lines):
if self.is_comment(lines[lineno]):
if lineno > 0:
lines[lineno] = "\0" + lines[lineno]
if lineno + 1 < len(lines):
lines[lineno] += "\0"
lineno += 1
text = "\n".join(lines)
text = re.sub(r"\0\n\0?", "\0", text)
lines = [line.rstrip() for line in text.split("\0")]
return [line for line in lines if line]
@staticmethod
def is_comment(line: str) -> bool:
"""
Determines whether a line is a comment.
Args:
line: The line to analyze.
Returns:
True if the line is a comment, False otherwise.
"""
return line.lstrip().startswith("#")
@staticmethod
def collapse_spaces(text: str) -> str:
"""
Collapses all consecutive space and tab characters of a string to a single space character.
Args:
text: The text to perform the operation on.
Returns:
The text with consecutive space and tab characters collapsed.
"""
# replace consecutive newlines with a null placeholder
text = text.replace("\n", "\0")
# collapse all runs of whitespace into a single space
text = re.sub(r"[ \t]+", " ", text.strip())
# reinsert consecutive newlines
return text.replace("\0", "\n")
@staticmethod
def capitalise(text: str) -> str:
"""
Capitalizes each sentence in a string.
Args:
text: The text to perform sentence capitalization on.
Returns:
The text after each sentence has been capitalized.
"""
return ". ".join(sentence.capitalize() for sentence in text.split(". "))
@staticmethod
def word_wrap(text: str) -> str:
"""
Wordwraps text using module-specific settings.
Args:
text: The text to be wordwrapped.
Returns:
The text with wordwrapping applied.
"""
return textwrap.fill(
text, width=79, drop_whitespace=True, break_long_words=False, break_on_hyphens=False
)
Attribute is_client: bool
inherited
property
readonly
¶
True if acting as a client, False otherwise.
Attribute is_server: bool
inherited
property
readonly
¶
True if acting as a server, False otherwise.
Attribute is_word_wrapping: bool
property
writable
¶
Specifies whether text should be word wrapped during editing or not.
Method __init__(self, *args, *, output_format, **kwargs)
special
¶
Defines the constructor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args |
Any |
Positional arguments to be passed to the parent constructor. |
() |
output_format |
str |
The output format to be used. |
required |
**kwargs |
Any |
Key-word only arguments to be passed to the parent constructor. |
{} |
Exceptions:
| Type | Description |
|---|---|
ValueError |
Editor or pager not found. |
Source code in mudproto/mpi.py
def __init__(self, *args: Any, output_format: str, **kwargs: Any) -> None:
"""
Defines the constructor.
Args:
*args: Positional arguments to be passed to the parent constructor.
output_format: The output format to be used.
**kwargs: Key-word only arguments to be passed to the parent constructor.
Raises:
ValueError: Editor or pager not found.
"""
self.output_format: str = output_format
super().__init__(*args, **kwargs)
self.state: MPIState = MPIState.DATA
"""The state of the state machine."""
self._mpi_buffer: bytearray = bytearray()
self._mpi_threads: list[threading.Thread] = []
self.command_map: MPICommandMapType = {
b"E": self.edit,
b"V": self.view,
}
"""A mapping of bytes to callables."""
editors: dict[str, str] = {
"win32": "notepad.exe",
}
pagers: dict[str, str] = {
"win32": "notepad.exe",
}
default_editor: str = editors.get(sys.platform, "nano")
default_pager: str = pagers.get(sys.platform, "less")
editor: str | None = shutil.which(os.getenv("VISUAL", "") or os.getenv("EDITOR", default_editor))
pager: str | None = shutil.which(os.getenv("PAGER", default_pager))
self._is_word_wrapping: bool = False
if editor is None: # pragma: no cover
raise ValueError("MPI editor executable not found.")
if pager is None: # pragma: no cover
raise ValueError("MPI pager executable not found.")
self.editor: str = editor
"""The program to use for editing received text."""
self.pager: str = pager
"""The program to use for viewing received read-only text."""
Method capitalise(text)
staticmethod
¶
Capitalizes each sentence in a string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text |
str |
The text to perform sentence capitalization on. |
required |
Returns:
| Type | Description |
|---|---|
str |
The text after each sentence has been capitalized. |
Source code in mudproto/mpi.py
@staticmethod
def capitalise(text: str) -> str:
"""
Capitalizes each sentence in a string.
Args:
text: The text to perform sentence capitalization on.
Returns:
The text after each sentence has been capitalized.
"""
return ". ".join(sentence.capitalize() for sentence in text.split(". "))
Method collapse_spaces(text)
staticmethod
¶
Collapses all consecutive space and tab characters of a string to a single space character.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text |
str |
The text to perform the operation on. |
required |
Returns:
| Type | Description |
|---|---|
str |
The text with consecutive space and tab characters collapsed. |
Source code in mudproto/mpi.py
@staticmethod
def collapse_spaces(text: str) -> str:
"""
Collapses all consecutive space and tab characters of a string to a single space character.
Args:
text: The text to perform the operation on.
Returns:
The text with consecutive space and tab characters collapsed.
"""
# replace consecutive newlines with a null placeholder
text = text.replace("\n", "\0")
# collapse all runs of whitespace into a single space
text = re.sub(r"[ \t]+", " ", text.strip())
# reinsert consecutive newlines
return text.replace("\0", "\n")
Method edit(self, data)
¶
Edits text using the program defined in editor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
bytes |
Received data from Mume, containing the session, description, and body of the text. |
required |
Source code in mudproto/mpi.py
def edit(self, data: bytes) -> None:
"""
Edits text using the program defined in `editor`.
Args:
data: Received data from Mume, containing the session, description, and body of the text.
"""
# Use windows line endings when editing the file.
newline: str = "\r\n"
# The MUME server sends the MPI data encoded in Latin-1.
session, _, body = str(data, "latin-1")[1:].split("\n", 2)
with tempfile.NamedTemporaryFile(
"w", encoding="utf-8", newline=newline, prefix="mume_editing_", suffix=".txt", delete=False
) as temp_file_obj:
file_path = Path(temp_file_obj.name)
temp_file_obj.write(body)
last_modified = file_path.stat().st_mtime
if self.output_format == "tintin":
print(f"MPICOMMAND:{self.editor} {file_path}:MPICOMMAND")
input("Continue:")
else:
subprocess.run((*self.editor.split(), str(file_path))) # NOQA: PLW1510, S603
response: str
if file_path.stat().st_mtime == last_modified:
# The user closed the text editor without saving. Cancel the editing session.
response = f"C{session}\n"
else:
if self.is_word_wrapping:
with file_path.open(encoding="utf-8", newline=newline) as file_obj:
text: str = file_obj.read()
text = self.postprocess(text)
with file_path.open("w", encoding="utf-8", newline=newline) as file_obj:
file_obj.write(text)
with file_path.open(encoding="utf-8", newline=newline) as file_obj:
response = f"E{session}\n{file_obj.read().strip()}\n"
file_path.unlink(missing_ok=True)
# MUME requires that output body be encoded in Latin-1 with Unix line endings.
output: bytes = bytes(response, "latin-1").replace(CR, b"")
self.write(MPI_INIT + b"E" + b"%d" % len(output) + LF + output)
Method get_paragraphs(self, text)
¶
Extracts paragraphs from a string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text |
str |
The text to analyze. |
required |
Returns:
| Type | Description |
|---|---|
list[str] |
The extracted paragraphs. |
Source code in mudproto/mpi.py
def get_paragraphs(self, text: str) -> list[str]:
"""
Extracts paragraphs from a string.
Args:
text: The text to analyze.
Returns:
The extracted paragraphs.
"""
lines: list[str] = text.splitlines()
lineno: int = 0
while lineno < len(lines):
if self.is_comment(lines[lineno]):
if lineno > 0:
lines[lineno] = "\0" + lines[lineno]
if lineno + 1 < len(lines):
lines[lineno] += "\0"
lineno += 1
text = "\n".join(lines)
text = re.sub(r"\0\n\0?", "\0", text)
lines = [line.rstrip() for line in text.split("\0")]
return [line for line in lines if line]
Method is_comment(line)
staticmethod
¶
Determines whether a line is a comment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
line |
str |
The line to analyze. |
required |
Returns:
| Type | Description |
|---|---|
bool |
True if the line is a comment, False otherwise. |
Source code in mudproto/mpi.py
@staticmethod
def is_comment(line: str) -> bool:
"""
Determines whether a line is a comment.
Args:
line: The line to analyze.
Returns:
True if the line is a comment, False otherwise.
"""
return line.lstrip().startswith("#")
Method on_command(self, command, data)
¶
Called when an MPI command is received.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
command |
bytes |
The MPI command, consisting of a single byte. |
required |
data |
bytes |
The payload. |
required |
Source code in mudproto/mpi.py
def on_command(self, command: bytes, data: bytes) -> None:
"""
Called when an MPI command is received.
Args:
command: The MPI command, consisting of a single byte.
data: The payload.
"""
if command not in self.command_map:
logger.warning(f"Invalid MPI command {command!r}.")
self.on_unhandled_command(command, data)
elif self.command_map[command] is not None:
thread = threading.Thread(target=self.command_map[command], args=(data,), daemon=True)
self._mpi_threads.append(thread)
thread.start()
Method on_connection_lost(self)
¶
Called by disconnect when a connection to peer has been lost.
Source code in mudproto/mpi.py
def on_connection_lost(self) -> None: # NOQA: D102
# Clean up any active editing sessions.
for thread in self._mpi_threads:
thread.join()
self._mpi_threads.clear()
Method on_connection_made(self)
¶
Called by connect when a connection to peer has been established.
Source code in mudproto/mpi.py
def on_connection_made(self) -> None: # NOQA: D102
# Identify for Mume Remote Editing.
self.write(MPI_INIT + b"I" + LF)
Method on_data_received(self, data)
¶
Called by parse when data is received.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
bytes |
The received data. |
required |
Source code in mudproto/mpi.py
def on_data_received(self, data: bytes) -> None: # NOQA: C901, D102, PLR0912, PLR0915
app_data_buffer: bytearray = bytearray()
while data:
if self.state is MPIState.DATA:
app_data, separator, data = data.partition(LF)
app_data_buffer.extend(app_data + separator)
if separator:
self.state = MPIState.NEWLINE
elif self.state is MPIState.NEWLINE:
if MPI_INIT.startswith(data[: len(MPI_INIT)]):
# Data starts with some or all of the MPI_INIT sequence.
self.state = MPIState.INIT
else:
self.state = MPIState.DATA
elif self.state is MPIState.INIT:
remaining = len(MPI_INIT) - len(self._mpi_buffer)
self._mpi_buffer.extend(data[:remaining])
data = data[remaining:]
if self._mpi_buffer == MPI_INIT:
# The final byte in the MPI_INIT sequence has been reached.
if app_data_buffer:
super().on_data_received(bytes(app_data_buffer))
app_data_buffer.clear()
self._mpi_buffer.clear()
self.state = MPIState.COMMAND
elif not MPI_INIT.startswith(self._mpi_buffer):
# The Bytes in the buffer are not part of an MPI init sequence.
data = bytes(self._mpi_buffer) + data
self._mpi_buffer.clear()
self.state = MPIState.DATA
elif self.state is MPIState.COMMAND:
# The MPI command is a single byte.
self._command, data = data[:1], data[1:]
self.state = MPIState.LENGTH
elif self.state is MPIState.LENGTH:
length, separator, data = data.partition(LF)
self._mpi_buffer.extend(length)
if not self._mpi_buffer.isdigit():
logger.warning(f"Invalid data {self._mpi_buffer!r} in MPI length. Digit expected.")
data = MPI_INIT + self._command + bytes(self._mpi_buffer) + separator + data
del self._command
self._mpi_buffer.clear()
self.state = MPIState.DATA
elif separator:
# The buffer contains the length of subsequent bytes to be received.
self._length = int(self._mpi_buffer)
self._mpi_buffer.clear()
self.state = MPIState.BODY
elif self.state is MPIState.BODY:
remaining = self._length - len(self._mpi_buffer)
self._mpi_buffer.extend(data[:remaining])
data = data[remaining:]
if len(self._mpi_buffer) == self._length:
# The final byte in the expected MPI data has been received.
self.on_command(self._command, bytes(self._mpi_buffer))
del self._command
del self._length
self._mpi_buffer.clear()
self.state = MPIState.DATA
if app_data_buffer:
super().on_data_received(bytes(app_data_buffer))
Method on_unhandled_command(self, command, data)
¶
Called for commands for which no handler is installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
command |
bytes |
The MPI command, consisting of a single byte. |
required |
data |
bytes |
The payload. |
required |
Source code in mudproto/mpi.py
def on_unhandled_command(self, command: bytes, data: bytes) -> None:
"""
Called for commands for which no handler is installed.
Args:
command: The MPI command, consisting of a single byte.
data: The payload.
"""
super().on_data_received(MPI_INIT + command + b"%d" % len(data) + LF + data)
Method postprocess(self, text)
¶
Reformats text before it is sent to the game when wordwrapping is enabled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text |
str |
The text to be processed. |
required |
Returns:
| Type | Description |
|---|---|
str |
The text with formatting applied. |
Source code in mudproto/mpi.py
def postprocess(self, text: str) -> str:
"""
Reformats text before it is sent to the game when wordwrapping is enabled.
Args:
text: The text to be processed.
Returns:
The text with formatting applied.
"""
paragraphs: list[str] = self.get_paragraphs(text)
for i, paragraph in enumerate(paragraphs):
if not self.is_comment(paragraph):
paragraphs[i] = self.word_wrap(self.capitalise(self.collapse_spaces(paragraph)))
return "\n".join(paragraphs)
Method view(self, data)
¶
Views text using the program defined in pager.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
bytes |
Received data from Mume, containing the text. |
required |
Source code in mudproto/mpi.py
def view(self, data: bytes) -> None:
"""
Views text using the program defined in `pager`.
Args:
data: Received data from Mume, containing the text.
"""
# Use windows line endings when viewing the file.
newline: str = "\r\n"
# The MUME server sends the MPI data encoded in Latin-1.
body: str = str(data, "latin-1")
with tempfile.NamedTemporaryFile(
"w", encoding="utf-8", newline=newline, prefix="mume_viewing_", suffix=".txt", delete=False
) as file_obj:
file_path = Path(file_obj.name)
file_obj.write(body)
if self.output_format == "tintin":
print(f"MPICOMMAND:{self.pager} {file_path}:MPICOMMAND")
else:
subprocess.run((*self.pager.split(), str(file_path))) # NOQA: PLW1510, S603
file_path.unlink(missing_ok=True)
Method word_wrap(text)
staticmethod
¶
Wordwraps text using module-specific settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text |
str |
The text to be wordwrapped. |
required |
Returns:
| Type | Description |
|---|---|
str |
The text with wordwrapping applied. |
Source code in mudproto/mpi.py
@staticmethod
def word_wrap(text: str) -> str:
"""
Wordwraps text using module-specific settings.
Args:
text: The text to be wordwrapped.
Returns:
The text with wordwrapping applied.
"""
return textwrap.fill(
text, width=79, drop_whitespace=True, break_long_words=False, break_on_hyphens=False
)
Method write(self, data)
inherited
¶
Writes data to peer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
bytes |
The bytes to be written. |
required |
Source code in mudproto/mpi.py
def write(self, data: bytes) -> None:
"""
Writes data to peer.
Args:
data: The bytes to be written.
"""
self._writer(data)
Class MPIState(Enum)
¶
Valid states for the state machine.
Source code in mudproto/mpi.py
class MPIState(Enum):
"""Valid states for the state machine."""
DATA = auto()
NEWLINE = auto()
INIT = auto()
COMMAND = auto()
LENGTH = auto()
BODY = auto()