Skip to content

Commit 7684295

Browse files
committed
Interactive REPL for python -m tasauria
1 parent 2bad2b1 commit 7684295

File tree

5 files changed

+176
-3
lines changed

5 files changed

+176
-3
lines changed

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
aiohttp >= 3.11, < 4
2+
3+
# For __main__
24
click >= 8.1, < 9
5+
ipython

tasauria/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class VersionInfo(NamedTuple):
2424
micro: int
2525

2626

27-
version_info: VersionInfo = VersionInfo(major=1, minor=0, micro=5)
27+
version_info: VersionInfo = VersionInfo(major=1, minor=0, micro=6)
2828

2929
__title__ = 'tasauria'
3030
__author__ = 'scarletcafe'

tasauria/__main__.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
5+
tasauria.__main__
6+
==================
7+
8+
Script that is run when using `python -m tasauria`.
9+
10+
This drops you into an IPython shell where `emu` is an already-connected TASauria instance.
11+
12+
13+
:copyright: (c) 2025-present Devon (scarlet.cafe) R
14+
:license: MIT, see LICENSE for more details.
15+
16+
"""
17+
18+
import logging
19+
import sys
20+
import typing
21+
22+
import click
23+
import IPython
24+
from traitlets.config import Config
25+
26+
27+
STARTUP_SCRIPT = """
28+
29+
import click
30+
from tasauria import TASauria
31+
32+
click.secho('Attempting to connect on: ', fg='green', nl=False)
33+
click.secho(TASAURIA_URL, fg='blue', nl=False)
34+
click.secho(' ...', fg='green')
35+
36+
try:
37+
emu = TASauria(TASAURIA_URL)
38+
await emu.connect()
39+
except Exception as error:
40+
raise RuntimeError("Failed to connect to TASauria server") from error
41+
else:
42+
click.secho('Successfully connected, access emulator using ', fg='green', nl=False)
43+
click.secho('`', fg='black', nl=False)
44+
click.secho('emu', fg='white', nl=False)
45+
click.secho('`', fg='black')
46+
47+
VERSION_INFO = await emu.get_version_info()
48+
GAME_INFO = await emu.get_game_info()
49+
50+
if VERSION_INFO.is_development_version:
51+
BIZHAWK_VERSION = f'BizHawk {VERSION_INFO.git_hash} (dev)'
52+
else:
53+
BIZHAWK_VERSION = f'BizHawk {VERSION_INFO.stable_version}'
54+
55+
if GAME_INFO.loaded:
56+
click.secho(f'{BIZHAWK_VERSION} playing ', fg='green', nl=False)
57+
click.secho(GAME_INFO.name, fg='blue', nl=False)
58+
click.secho(f" ({GAME_INFO.hash})", fg='cyan', nl=False)
59+
click.secho(' on ', fg='green', nl=False)
60+
click.secho(GAME_INFO.system, fg='blue')
61+
else:
62+
click.secho("No game loaded", fg='green')
63+
64+
""".strip().split("\n\n")
65+
66+
67+
LOG_FORMAT: logging.Formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')
68+
LOG_STREAM: logging.Handler = logging.StreamHandler(stream=sys.stdout)
69+
LOG_STREAM.setFormatter(LOG_FORMAT)
70+
71+
72+
@click.command()
73+
@click.option('--log-level', '-v', default='INFO')
74+
@click.option('--log-file', '-l', default=None)
75+
@click.option('--port', '-p', default=20251)
76+
@click.option('--url', '-u', default=None)
77+
def entrypoint(
78+
log_level: str,
79+
log_file: typing.Optional[str] = None,
80+
port: int = 20251,
81+
url: typing.Optional[str] = None,
82+
):
83+
84+
logger = logging.getLogger()
85+
logger.setLevel(getattr(logging, log_level))
86+
logger.addHandler(LOG_STREAM)
87+
88+
if log_file:
89+
log_file_handler: logging.Handler = logging.FileHandler(filename=log_file, encoding='utf-8', mode='a')
90+
log_file_handler.setFormatter(LOG_FORMAT)
91+
logger.addHandler(log_file_handler)
92+
93+
if url is None:
94+
url = f"http://127.0.0.1:{port}/"
95+
96+
config = Config()
97+
config.InteractiveShellApp.exec_lines = [
98+
f"TASAURIA_URL = {url!r}",
99+
*STARTUP_SCRIPT
100+
]
101+
102+
IPython.start_ipython(
103+
using='asyncio',
104+
config=config
105+
)
106+
107+
108+
if __name__ == '__main__':
109+
entrypoint()

tasauria/client.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from tasauria.adapters.async_ import AsyncAdapter, HTTPAdapter, WebSocketAdapter
2121
from tasauria.commands import AnyCommand, Command, PythonCommandInput, ServerCommandInput, ServerCommandOutput, PythonCommandOutput
22-
from tasauria.commands.client import ClientFrameStatusCommand, ClientFrameAdvanceCommand, ClientGameCommand, FrameStatus, GameInfo
22+
from tasauria.commands.client import ClientFrameStatusCommand, ClientFrameAdvanceCommand, ClientGameCommand, ClientVersionCommand, FrameStatus, GameInfo, VersionInfo
2323
from tasauria.commands.joypad import JoypadGetCommand, JoypadSetCommand
2424
from tasauria.commands.memory import MemoryReadFloatCommand, MemoryReadIntegerCommand, MemoryReadRangeCommand, MemoryWriteFloatCommand, MemoryWriteIntegerCommand, MemoryWriteRangeCommand
2525
from tasauria.commands.meta import MetaBatchCommand, MetaPingCommand
@@ -221,6 +221,20 @@ async def get_game_info(
221221
ClientGameCommand
222222
)
223223

224+
async def get_version_info(
225+
self
226+
) -> VersionInfo:
227+
"""
228+
<section>client</section>
229+
230+
<description language="en">
231+
Gets information about the client version.
232+
</description>
233+
"""
234+
return await self._execute_command(
235+
ClientVersionCommand
236+
)
237+
224238
# -- Joypad --
225239
async def get_joypad(
226240
self,

tasauria/commands/client.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def unmarshal_output(
5656

5757

5858
class ClientFrameAdvanceInput(typing.TypedDict):
59-
unpause: bool
59+
unpause: typing.Optional[bool]
6060

6161

6262
class ClientFrameAdvanceCommand(Command[ClientFrameAdvanceInput, ClientFrameAdvanceInput, ClientFrameStatusServerOutput, FrameStatus]):
@@ -141,3 +141,50 @@ def unmarshal_output(
141141
database_status_bad=payload["databaseStatusBad"],
142142
game_options=payload["gameOptions"],
143143
)
144+
145+
146+
class ClientVersionServerOutput(typing.TypedDict):
147+
stableVersion: str
148+
releaseDate: str
149+
gitBranch: str
150+
gitHash: str
151+
gitRevision: str
152+
isDevelopmentVersion: bool
153+
customBuildString: typing.Optional[str]
154+
155+
156+
@dataclasses.dataclass
157+
class VersionInfo:
158+
stable_version: str
159+
release_date: str
160+
git_branch: str
161+
git_hash: str
162+
git_revision: str
163+
is_development_version: bool
164+
custom_build_string: typing.Optional[str]
165+
166+
167+
class ClientVersionCommand(Command[NoArguments, NoArguments, ClientVersionServerOutput, VersionInfo]):
168+
@staticmethod
169+
def marshal_input(
170+
**kwargs: typing.Any,
171+
) -> typing.Tuple[str, NoArguments]:
172+
return (
173+
"/client/version",
174+
{}
175+
)
176+
177+
@staticmethod
178+
def unmarshal_output(
179+
payload: ClientVersionServerOutput,
180+
**kwargs: typing.Any
181+
) -> VersionInfo:
182+
return VersionInfo(
183+
stable_version=payload["stableVersion"],
184+
release_date=payload["releaseDate"],
185+
git_branch=payload["gitBranch"],
186+
git_hash=payload["gitHash"],
187+
git_revision=payload["gitRevision"],
188+
is_development_version=payload["isDevelopmentVersion"],
189+
custom_build_string=payload["customBuildString"],
190+
)

0 commit comments

Comments
 (0)