creation of a new web worker fails if the script is in a VFS and is set using the `src` attribute
Opened this issue · 0 comments
moepnse commented
Hello,
the creation of a new web worker fails if the script is in a VFS and is set using the src
attribute.
If I change the following line from:
s.src = file
to
s.innerText = open(file, "r").read()
then at least the web worker can be started.
If the line worker_script_path = "py/elephas/web_workers/formatter.py"
is changed to worker_script_path = "formatter0.py"
, then the worker works as desired. In contrast to formatter.py
, formatter0.py
is not Base64 encoded and is not located in the VFS. However, both are identical in terms of functionality.
Thanks in advance for your help!
Error:
Traceback (most recent call last):
File "brython_issue_20240917_1.html#webworker", line 105, in onload
self._workers.append(WWPoolWorker(wid, self))
^^^^^^^^^^^^^^^^^^^^^^^
File "brython_issue_20240622.html#webworker", line 52, in __init__
worker.create_worker(wid, self.onready, self.onmessage, self.onerror)
RuntimeError: No webworker with id 'webworker_26abd947_63b6_4d46_9e26_ee13fd35c4ef'
brython_issue_20240917_1.html
<!DOCTYPE html>
<html>
<head>
<!-- Required meta tags-->
<meta charset="utf-8">
<title>Brython - Issue Demo 20240917</title>
<!-- Brython -->
<script src="https://raw.githack.com/brython-dev/brython/master/www/src/brython.js"></script>
<script src="https://raw.githack.com/brython-dev/brython/master/www/src/brython_stdlib.js"></script>
<!-- <script type="text/python" class="webworker" id="formatter" src="formatter.py"></script>-->
<script src="stermi.vfs.js"></script>
<script type="text/python" id="webworker">
import uuid
from browser import document, console, worker, window
class WebWorker:
_busy: bool = False
_rdy: bool = False
_msgs: list = None
_w = None
def __init__(self, src):
self._msgs = []
s = document.createElement("script")
s.setAttribute("type", "text/python")
s.className = "webworker"
s.id = "webworker_" + uuid.uuid4()
document.head.appendChild(s)
worker.create_worker(s.id, self.onready, self.onmessage, self.onerror)
def onready(self, worker):
self._w = worker
self._rdy = True
if len(self._msgs) > 0:
for msg in self._msgs:
self._w.send(msg)
def send(self, msg):
self._busy = True
if not self._rdy:
console.debug("worker not yet ready...")
self._msgs.append(msg)
else:
console.debug("sending message to worker...")
self._w.send(msg)
def onmessage(self, evt):
pass
def onerror(self, evt):
pass
class WWPoolWorker(WebWorker):
def __init__(self, wid, pool_leader):
self._pool_leader = pool_leader
self._msgs = []
self._id = wid
element = document.getElementById(wid)
console.debug("element:", element)
src = element.src
console.debug("src:", src)
console.debug("creating new worker...")
worker.create_worker(wid, self.onready, self.onmessage, self.onerror)
def onmessage(self, evt):
self._busy = False
self._pool_leader.onmessage(self, self._rid, evt)
def send(self, msg, rid):
console.debug("new message:", msg, rid)
self._busy = True
self._rid = rid
self._w.send(msg)
def onready(self, worker):
super().onready(worker)
self._pool_leader.send_pending_msgs()
def onerror(self, error_msg):
super().onready(worker)
console.error(self._id, error_msg)
class WWPool:
_msgs: list = None
_promises: dict = None
def __init__(self, src=None, file=None, wid=None, worker_count=5):
self._msgs = []
self._promises = {}
self._workers = []
if wid is not None:
self._id = wid
for i in range(worker_count):
console.debug(f"starting new pool worker {wid}...")
self._workers.append(WWPoolWorker(wid, self))
elif file is not None or src is not None:
s = document.createElement("script")
s.className = "webworker"
new_uuid = str(uuid.uuid4()).replace("-", "_")
wid = s.id = "webworker_" + new_uuid
s.setAttribute("type", "text/python")
"""
def observe(records, obs):
if records[0].addedNodes[0].id == wid:
for i in range(worker_count):
console.debug("starting new pool worker...")
self._workers.append(WWPoolWorker(wid, self))
observer.disconnect()
observer = window.MutationObserver.new(observe)
observer.observe(document.body, {'childList': True})
"""
def onload(ev):
console.debug("web worker onload:", ev)
for i in range(worker_count):
console.debug(f"starting new pool worker {wid}...")
self._workers.append(WWPoolWorker(wid, self))
s.removeEventListener("load", onload)
s.addEventListener("load", onload)
if src is not None:
console.debug(f"setting src of worker {wid}...")
s.innerText = src
elif file is not None:
# does currently not work, see #2215 for more informations
console.debug(f"setting target script of worker {wid} to {file}...")
s.src = file
#s.innerText = open(file, "r").read()
#document.body.appendChild(s)
document <= s
else:
pass
#if wid is not None:
# for i in range(worker_count):
# console.debug("starting new pool worker...")
# self._workers.append(WWPoolWorker(wid, self))
def send(self, msg):
new_uuid = str(uuid.uuid4())
def promise_func(resolve, reject):
console.debug("new promise:", new_uuid)
self._promises[new_uuid] = resolve
for worker in self._workers:
if not worker._busy and worker._rdy:
console.debug(f"sending message {new_uuid} to worker:", worker)
worker.send(msg, new_uuid)
break
else:
console.debug(f"no worker available! adding message {new_uuid} to queue...")
self._msgs.append((new_uuid, msg))
new_promise = window.Promise.new(promise_func)
return new_promise
def send_pending_msgs(self):
console.debug("pending messages:", len(self._msgs), self._msgs)
for worker in self._workers:
if len(self._msgs) == 0:
console.debug("message queue is empty! aborting...")
return
if not worker._busy and worker._rdy:
new_uuid, msg = self._msgs.pop(0)
console.debug(f"sending pending message {new_uuid} to worker:", worker)
worker.send(msg, new_uuid)
def onmessage(self, woker, rid, evt):
console.debug("received message from worker:", rid, evt, evt.data)
console.debug("promises:", self._promises)
promise = self._promises[rid]
console.debug("promise:", promise)
promise(evt.data)
del self._promises[rid]
self.send_pending_msgs()
</script>
<script type="text/python" id="formatter_lib">
# Standard library imports.
import os
from browser import console, window
import webworker as ww
# Related third party imports.
# Local application/library specific imports.
console.debug("creating webworker pool...")
worker_script_path = "py/elephas/web_workers/formatter.py"
#worker_script_path = "formatter0.py"
with open(worker_script_path) as fh:
console.debug(fh.read())
wwpool = ww.WWPool(file=worker_script_path)
console.debug("Done!")
def ww_format(string, **data):
console.debug("formatting string:", string, data)
formatted_str = wwpool.send([string, data])
console.debug("promise:", formatted_str)
return formatted_str
</script>
</head>
<body onload="brython({debug: 2});">
<script type="text/python">
import json
import formatter_lib
from browser import console, aio, document
async def format():
mapping = json.loads(document["mapping"].value)
ta_in = document["in"]
ta_out = document["out"]
ta_out.value = await formatter_lib.ww_format(ta_in.value, **mapping)
def btn_format_click(evt):
aio.run(format())
document["btn_format"].addEventListener("click", btn_format_click)
def btn_ww_info_click(evt):
console.debug(formatter_lib.wwpool._msgs)
for worker in formatter_lib.wwpool._workers:
console.debug(worker._rdy, worker._busy)
document["btn_ww_info"].addEventListener("click", btn_ww_info_click)
</script>
<textarea id="mapping">{"test": "test123"}</textarea>
<textarea id="in">test %test% \%test\%</textarea>
<textarea id="out"></textarea>
<div id="container"></div>
<button id="btn_format">Format</button>
<button id="btn_ww_info">WW Info</button>
</body>
</html>
stermi.vfs.js
__BRYTHON__.use_VFS = true;
__BRYTHON__.add_files({
"py/elephas/web_workers/formatter.py": {
"content": "IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwoKIyBTdGFuZGFyZCBsaWJyYXJ5IGltcG9ydHMuCmZyb20gY29sbGVjdGlvbnMgaW1wb3J0IGRlcXVlCiNmcm9tIGJyb3dzZXIud2Vid29ya2VyIGltcG9ydCBjdXJyZW50X3dvcmtlciwgTWVzc2FnZQpmcm9tIHN5cyBpbXBvcnQgYXJndgpmcm9tIG9zIGltcG9ydCBlbnZpcm9uCmltcG9ydCBodG1sCiMgSW4gd2ViIHdvcmtlcnMsICJ3aW5kb3ciIGlzIHJlcGxhY2VkIGJ5ICJzZWxmIi4KZnJvbSBicm93c2VyIGltcG9ydCBiaW5kLCBzZWxmLCBjb25zb2xlCmltcG9ydCBqc29uCgojIFJlbGF0ZWQgdGhpcmQgcGFydHkgaW1wb3J0cy4KCiMgTG9jYWwgYXBwbGljYXRpb24vbGlicmFyeSBzcGVjaWZpYyBpbXBvcnRzLgojZnJvbSBzdGVybWkgaW1wb3J0IGZvcm1hdHRlcgoKY29uc29sZS5kZWJ1Zygic3RhcnRlZCBmb3JtYXQgd2ViIHdvcmtlciIpCgoKZGVmIGZvcm1hdChzdHJpbmc6IHN0ciwgZGF0YSk6CiAgICBzdGFydF90b2tlbnM6IGxpc3Q9WyIlIiwgInsiXQogICAgZW5kX3Rva2VuczogbGlzdD1bIiUiLCAifSJdCiAgICBzdGFjayA9IGRlcXVlKFtdLCAyKQogICAgc3RhcnRfdG9rZW5zX2NoYXJzOiBzdHIgPSAiIi5qb2luKHN0YXJ0X3Rva2VucykKICAgIGVuZF90b2tlbnNfY2hhcnM6IHN0ciA9ICIiLmpvaW4oZW5kX3Rva2VucykKICAgIHN0YXJ0X3Rva2VuX2ZvdW5kOiBib29sID0gRmFsc2UKICAgIGVuZF90b2tlbl9mb3VuZDogYm9vbCA9IEZhbHNlCiAgICB2YXJfbmFtZTogc3RyID0gIiIKICAgIGFsbG93ZWRfY2hhcnM6IHN0ciA9ICJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejEyMzQ1Njc4OTBfIgogICAgbGFzdF9jaGFyOiBzdHIgPSAiIgogICAgc3RhcnRfdG9rZW46IHN0ciA9ICIiCiAgICBlbmRfdG9rZW46IHN0ciA9ICIiCiAgICBuZXdfc3RyaW5nOiBzdHIgPSAiIgogICAgZm9yIGMgaW4gc3RyaW5nOgogICAgICAgIGlmIChub3Qgc3RhcnRfdG9rZW5fZm91bmQgYW5kIGMgaW4gc3RhcnRfdG9rZW5zX2NoYXJzKSBvciAoc3RhcnRfdG9rZW5fZm91bmQgYW5kIGMgaW4gZW5kX3Rva2Vuc19jaGFycykgYW5kIGxhc3RfY2hhciAhPSAiXFwiOgogICAgICAgICAgICBzdGFjay5hcHBlbmQoYykKICAgICAgICBlbHNlOgogICAgICAgICAgICBzdGFjay5hcHBlbmQoIiAiKQogICAgICAgIHRva2VuID0gIiIuam9pbihzdGFjaykKICAgICAgICBpZiBzdGFydF90b2tlbl9mb3VuZDoKICAgICAgICAgICAgZm9yIHQgaW4gKGMsIHRva2VuKToKICAgICAgICAgICAgICAgIGlmIHQgaW4gZW5kX3Rva2VuczoKICAgICAgICAgICAgICAgICAgICBpZiBzdGFydF90b2tlbnMuaW5kZXgoc3RhcnRfdG9rZW4pID09IGVuZF90b2tlbnMuaW5kZXgodCk6CiAgICAgICAgICAgICAgICAgICAgICAgIGVuZF90b2tlbiA9IHQKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBkYXRhLmdldCh2YXJfbmFtZSwgZiJ7c3RhcnRfdG9rZW59e3Zhcl9uYW1lfXtlbmRfdG9rZW59IikKICAgICAgICAgICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZSh2YWx1ZSwgKGxpc3QsIGRpY3QpKToKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0ganNvbi5kdW1wcyh2YWx1ZSkKICAgICAgICAgICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gc3RyKHZhbHVlKQogICAgICAgICAgICAgICAgICAgICAgICBuZXdfc3RyaW5nICs9IGh0bWwuZXNjYXBlKHZhbHVlKQogICAgICAgICAgICAgICAgICAgICAgICBzdGFydF90b2tlbl9mb3VuZCA9IEZhbHNlCiAgICAgICAgICAgICAgICAgICAgICAgIGVuZF90b2tlbl9mb3VuZCA9IFRydWUKICAgICAgICAgICAgICAgICAgICAgICAgdmFyX25hbWUgPSAiIgogICAgICAgICAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIHByaW50KCJlbmQgdG9rZW4gZG9lcyBub3QgbWF0Y2ggc3RhcnQgdG9rZW4hIikKICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnRfdG9rZW5fZm91bmQgPSBGYWxzZQogICAgICAgICAgICAgICAgICAgICAgICBuZXdfc3RyaW5nICs9IHN0YXJ0X3Rva2VuICsgdmFyX25hbWUgKyBjCiAgICAgICAgICAgICAgICAgICAgICAgIHZhcl9uYW1lID0gIiIKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGlmIGMgaW4gZW5kX3Rva2Vuc19jaGFyczoKICAgICAgICAgICAgICAgICAgICBsYXN0X2NoYXIgPSBjCiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgIGlmIGMgaW4gYWxsb3dlZF9jaGFyczoKICAgICAgICAgICAgICAgICAgICB2YXJfbmFtZSArPSBjCiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgc3RhcnRfdG9rZW5fZm91bmQgPSBGYWxzZQogICAgICAgICAgICAgICAgICAgIHByaW50KGYie2N9IGlzIG5vdCBhbGxvd2VkIGluc2lkZSBwbGFjZWhvbGRlciIpCiAgICAgICAgICAgICAgICAgICAgbmV3X3N0cmluZyArPSBzdGFydF90b2tlbiArIHZhcl9uYW1lICsgYwogICAgICAgICAgICAgICAgICAgIHZhcl9uYW1lID0gIiIKICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgIGlmIGVuZF90b2tlbl9mb3VuZDoKICAgICAgICAgICAgICAgIGxhc3RfY2hhciA9IGMKICAgICAgICAgICAgICAgIGVuZF90b2tlbl9mb3VuZCA9IEZhbHNlCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgIGlmIG5vdCBzdGFydF90b2tlbl9mb3VuZDoKICAgICAgICAgICAgZm9yIHQgaW4gKGMsIHRva2VuKToKICAgICAgICAgICAgICAgIGlmIHQgaW4gc3RhcnRfdG9rZW5zOgogICAgICAgICAgICAgICAgICAgIHN0YXJ0X3Rva2VuX2ZvdW5kID0gVHJ1ZQogICAgICAgICAgICAgICAgICAgIHN0YXJ0X3Rva2VuID0gdAogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgaWYgYyBub3QgaW4gc3RhcnRfdG9rZW5zX2NoYXJzIGFuZCBub3QgKHN0YXJ0X3Rva2VuX2ZvdW5kIGFuZCBjIGluIGVuZF90b2tlbnNfY2hhcnMpIG9yIGxhc3RfY2hhciA9PSAiXFwiOgogICAgICAgICAgICBuZXdfc3RyaW5nICs9IGMKICAgICAgICBsYXN0X2NoYXIgPSBjCiAgICByZXR1cm4gbmV3X3N0cmluZwoKCmRlZiBmb3JtYXRfd2ViKHN0cmluZzogc3RyLCBkYXRhKToKICAgIHN0YXJ0X3Rva2VuX2ZvdW5kOiBib29sID0gRmFsc2UKICAgIHZhcl9uYW1lOiBzdHIgPSAiIgogICAgYWxsb3dlZF9jaGFyczogc3RyID0gImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6MTIzNDU2Nzg5MF8iCiAgICBsYXN0X2NoYXI6IHN0ciA9ICIiCiAgICBzdGFydF90b2tlbjogc3RyID0gIiUiCiAgICBlbmRfdG9rZW46IHN0ciA9ICIlIgogICAgbmV3X3N0cmluZzogc3RyID0gIiIKICAgIGZvciBjIGluIHN0cmluZzoKICAgICAgICBpZiBzdGFydF90b2tlbl9mb3VuZDoKICAgICAgICAgICAgaWYgYyA9PSBlbmRfdG9rZW46CiAgICAgICAgICAgICAgICB2YWx1ZSA9IGRhdGEuZ2V0KHZhcl9uYW1lLCBmIntzdGFydF90b2tlbn17dmFyX25hbWV9e2VuZF90b2tlbn0iKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZSh2YWx1ZSwgKGxpc3QsIGRpY3QpKToKICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBqc29uLmR1bXBzKHZhbHVlKQogICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IHN0cih2YWx1ZSkKICAgICAgICAgICAgICAgIG5ld19zdHJpbmcgKz0gaHRtbC5lc2NhcGUodmFsdWUpCiAgICAgICAgICAgICAgICBzdGFydF90b2tlbl9mb3VuZCA9IEZhbHNlCiAgICAgICAgICAgICAgICB2YXJfbmFtZSA9ICIiCiAgICAgICAgICAgICAgICBsYXN0X2NoYXIgPSBjCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgaWYgYyBpbiBhbGxvd2VkX2NoYXJzOgogICAgICAgICAgICAgICAgICAgIHZhcl9uYW1lICs9IGMKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICBzdGFydF90b2tlbl9mb3VuZCA9IEZhbHNlCiAgICAgICAgICAgICAgICAgICAgcHJpbnQoZiJ7Y30gaXMgbm90IGFsbG93ZWQgaW5zaWRlIHBsYWNlaG9sZGVyIikKICAgICAgICAgICAgICAgICAgICBuZXdfc3RyaW5nICs9IHN0YXJ0X3Rva2VuICsgdmFyX25hbWUgKyBjCiAgICAgICAgICAgICAgICAgICAgdmFyX25hbWUgPSAiIgogICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICBlbHNlOgogICAgICAgICAgICBpZiAgbGFzdF9jaGFyICE9ICJcXCIgYW5kIGMgPT0gc3RhcnRfdG9rZW46CiAgICAgICAgICAgICAgICBzdGFydF90b2tlbl9mb3VuZCA9IFRydWUKICAgICAgICAgICAgICAgIGxhc3RfY2hhciA9IGMKICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgaWYgYyAhPSBzdGFydF90b2tlbiBhbmQgbm90IChzdGFydF90b2tlbl9mb3VuZCBhbmQgYyA9PSBlbmRfdG9rZW4pIG9yIGxhc3RfY2hhciA9PSAiXFwiOgogICAgICAgICAgICBuZXdfc3RyaW5nICs9IGMKICAgICAgICBsYXN0X2NoYXIgPSBjCiAgICByZXR1cm4gbmV3X3N0cmluZwoKCkBiaW5kKHNlbGYsICJtZXNzYWdlIikKZGVmIG1lc3NhZ2UoZXZ0KToKICAgICIiIkhhbmRsZSBhIG1lc3NhZ2Ugc2VudCBieSB0aGUgbWFpbiBzY3JpcHQuCiAgICBldnQuZGF0YSBpcyB0aGUgbWVzc2FnZSBib2R5LgogICAgIiIiCiAgICBjb25zb2xlLmRlYnVnKCJuZXcgbWVzc2FnZSByZWNlaXZlZCIpCiAgICAjc2VsZi5zZW5kKGZvcm1hdCgqKmV2dC5kYXRhKSkKICAgIHNlbGYuc2VuZChmb3JtYXRfd2ViKGV2dC5kYXRhWzBdLCBldnQuZGF0YVsxXSkp\n",
"ctime": 1726502204.3286872,
"mtime": 1719139986.8821394
}
});
formatter0.py
#!/usr/bin/env python3
# Standard library imports.
from collections import deque
#from browser.webworker import current_worker, Message
from sys import argv
from os import environ
import html
# In web workers, "window" is replaced by "self".
from browser import bind, self, console
import json
# Related third party imports.
# Local application/library specific imports.
#from stermi import formatter
console.debug("started format web worker")
def format(string: str, data):
start_tokens: list=["%", "{"]
end_tokens: list=["%", "}"]
stack = deque([], 2)
start_tokens_chars: str = "".join(start_tokens)
end_tokens_chars: str = "".join(end_tokens)
start_token_found: bool = False
end_token_found: bool = False
var_name: str = ""
allowed_chars: str = "abcdefghijklmnopqrstuvwxyz1234567890_"
last_char: str = ""
start_token: str = ""
end_token: str = ""
new_string: str = ""
for c in string:
if (not start_token_found and c in start_tokens_chars) or (start_token_found and c in end_tokens_chars) and last_char != "\\":
stack.append(c)
else:
stack.append(" ")
token = "".join(stack)
if start_token_found:
for t in (c, token):
if t in end_tokens:
if start_tokens.index(start_token) == end_tokens.index(t):
end_token = t
value = data.get(var_name, f"{start_token}{var_name}{end_token}")
if isinstance(value, (list, dict)):
value = json.dumps(value)
else:
value = str(value)
new_string += html.escape(value)
start_token_found = False
end_token_found = True
var_name = ""
break
else:
print("end token does not match start token!")
start_token_found = False
new_string += start_token + var_name + c
var_name = ""
else:
if c in end_tokens_chars:
last_char = c
continue
if c in allowed_chars:
var_name += c
continue
else:
start_token_found = False
print(f"{c} is not allowed inside placeholder")
new_string += start_token + var_name + c
var_name = ""
continue
if end_token_found:
last_char = c
end_token_found = False
continue
if not start_token_found:
for t in (c, token):
if t in start_tokens:
start_token_found = True
start_token = t
break
if c not in start_tokens_chars and not (start_token_found and c in end_tokens_chars) or last_char == "\\":
new_string += c
last_char = c
return new_string
def format_web(string: str, data):
start_token_found: bool = False
var_name: str = ""
allowed_chars: str = "abcdefghijklmnopqrstuvwxyz1234567890_"
last_char: str = ""
start_token: str = "%"
end_token: str = "%"
new_string: str = ""
for c in string:
if start_token_found:
if c == end_token:
value = data.get(var_name, f"{start_token}{var_name}{end_token}")
if isinstance(value, (list, dict)):
value = json.dumps(value)
else:
value = str(value)
new_string += html.escape(value)
start_token_found = False
var_name = ""
last_char = c
continue
else:
if c in allowed_chars:
var_name += c
continue
else:
start_token_found = False
print(f"{c} is not allowed inside placeholder")
new_string += start_token + var_name + c
var_name = ""
continue
else:
if last_char != "\\" and c == start_token:
start_token_found = True
last_char = c
continue
if c != start_token and not (start_token_found and c == end_token) or last_char == "\\":
new_string += c
last_char = c
return new_string
@bind(self, "message")
def message(evt):
"""Handle a message sent by the main script.
evt.data is the message body.
"""
console.debug("new message received")
#self.send(format(**evt.data))
self.send(format_web(evt.data[0], evt.data[1]))