diff --git a/src/wokwi_client/client.py b/src/wokwi_client/client.py index 5d459eb..6f4ad4d 100644 --- a/src/wokwi_client/client.py +++ b/src/wokwi_client/client.py @@ -20,6 +20,7 @@ upload, upload_file, upload_idf_firmware, + upload_text, ) from .framebuffer import ( read_framebuffer_png_bytes, @@ -90,6 +91,19 @@ async def upload(self, name: str, content: bytes) -> None: """ await upload(self._transport, name, content) + async def upload_text(self, name: str, content: str) -> None: + """ + Upload a text file to the simulator from a string. + + Use this method for text-based files such as JSON chip definitions + (``*.chip.json``). For binary files, use :meth:`upload`. + + Args: + name: The name to use for the uploaded file. + content: The file content as a string. + """ + await upload_text(self._transport, name, content) + async def upload_file(self, filename: str, local_path: Optional[Path] = None) -> str: """ Upload a local file to the simulator. diff --git a/src/wokwi_client/client_sync.pyi b/src/wokwi_client/client_sync.pyi index a76f8a6..ca95594 100644 --- a/src/wokwi_client/client_sync.pyi +++ b/src/wokwi_client/client_sync.pyi @@ -38,6 +38,7 @@ class WokwiClientSync: # Methods mirrored from WokwiClient (sync variants) def upload(self, name: str, content: bytes) -> None: ... + def upload_text(self, name: str, content: str) -> None: ... def upload_file(self, filename: str, local_path: Path | None = None) -> str: ... def upload_idf_firmware(self, flasher_args_path: str | Path) -> IdfFirmwareUploadResult: ... def download(self, name: str) -> bytes: ... diff --git a/src/wokwi_client/file_ops.py b/src/wokwi_client/file_ops.py index 021b3c1..c45cf37 100644 --- a/src/wokwi_client/file_ops.py +++ b/src/wokwi_client/file_ops.py @@ -8,10 +8,12 @@ from wokwi_client.idf import resolveIdfFirmware -from .models import UploadParams +from .models import TextUploadParams, UploadParams from .protocol_types import ResponseMessage from .transport import Transport +_TEXT_EXTENSIONS = {".json", ".toml", ".txt", ".yaml", ".yml"} + class FlashSection: """A flash section with its offset and remote file name.""" @@ -33,8 +35,15 @@ async def upload_file( transport: Transport, filename: str, local_path: Optional[Path] = None ) -> str: firmware_path = local_path or filename - content = Path(firmware_path).read_bytes() - await upload(transport, filename, content) + file_path = Path(firmware_path) + # Use the remote filename's extension to determine the upload format, + # since the server's interpretation is based on the destination name. + if Path(filename).suffix in _TEXT_EXTENSIONS: + text_content = file_path.read_text(encoding="utf-8") + await upload_text(transport, filename, text_content) + else: + content = file_path.read_bytes() + await upload(transport, filename, content) return filename @@ -55,5 +64,10 @@ async def upload(transport: Transport, name: str, content: bytes) -> ResponseMes return await transport.request("file:upload", params.model_dump()) +async def upload_text(transport: Transport, name: str, content: str) -> ResponseMessage: + params = TextUploadParams(name=name, text=content) + return await transport.request("file:upload", params.model_dump()) + + async def download(transport: Transport, name: str) -> ResponseMessage: return await transport.request("file:download", {"name": name}) diff --git a/src/wokwi_client/models.py b/src/wokwi_client/models.py index e085a31..502bc0d 100644 --- a/src/wokwi_client/models.py +++ b/src/wokwi_client/models.py @@ -10,6 +10,11 @@ class UploadParams(BaseModel): binary: str # base64 +class TextUploadParams(BaseModel): + name: str + text: str + + class SimulationParams(BaseModel): firmware: str elf: str