Web server for node.js interpreter, added docstrings, unittest
This commit is contained in:
parent
a770df7334
commit
d36a0528ea
5 changed files with 127 additions and 16 deletions
|
@ -1,15 +1,20 @@
|
|||
"""Parsing and executing JavaScript code"""
|
||||
|
||||
import abc
|
||||
|
||||
import json
|
||||
import base64
|
||||
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union, Any
|
||||
from typing import Type
|
||||
from typing import Optional, Union
|
||||
from typing import Type, Any
|
||||
|
||||
import regex
|
||||
import js2py
|
||||
import requests
|
||||
|
||||
js: Optional['Interpreter'] = None
|
||||
|
||||
|
@ -18,9 +23,19 @@ class Interpreter(abc.ABC):
|
|||
"""Base JS interpreter class"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Base JS interpreter class"""
|
||||
pass
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
"""Support for `js[name]` syntax
|
||||
instead of `js.get_var(name)`
|
||||
|
||||
Args:
|
||||
name (str): Variable name
|
||||
|
||||
Returns:
|
||||
Variable value
|
||||
"""
|
||||
return self.get_var(name)
|
||||
|
||||
@abc.abstractmethod
|
||||
|
@ -48,24 +63,47 @@ class Interpreter(abc.ABC):
|
|||
|
||||
class NodeInterpreter(Interpreter):
|
||||
|
||||
def __init__(self, node: Union[str, Path]) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
node: Union[str, Path] = 'node',
|
||||
host: str = 'localhost',
|
||||
port: int = 8001) -> None:
|
||||
"""Node.JS interpreter wrapper,
|
||||
starts a simple web server in background
|
||||
|
||||
Args:
|
||||
node (Union[str, Path], optional): Path to `node` executable
|
||||
host (str, optional): Hostname for the web server
|
||||
port (int, optional): Port for the web server
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
|
||||
file_dir = Path(__file__).absolute().parent
|
||||
server_js = file_dir / 'data' / 'server.js'
|
||||
|
||||
self.url = f'http://{host}:{port}'
|
||||
|
||||
self.proc = subprocess.Popen(
|
||||
node,
|
||||
stdout=subprocess.PIPE,
|
||||
args=[
|
||||
node, server_js,
|
||||
f'{port}', host,
|
||||
],
|
||||
)
|
||||
time.sleep(0.1)
|
||||
|
||||
def exec_js(self, func: str) -> None:
|
||||
self.proc.communicate(func.encode('utf-8'))
|
||||
resp = requests.post(self.url, data=func)
|
||||
resp.raise_for_status()
|
||||
|
||||
def get_var(self, name: str) -> Any:
|
||||
assert self.proc.stdout is not None
|
||||
self.proc.stdout.read()
|
||||
self.proc.communicate(name.encode('utf-8'))
|
||||
return self.proc.stdout.read().decode('utf-8')
|
||||
resp = requests.post(self.url, data=name)
|
||||
resp.raise_for_status()
|
||||
return json.loads(resp.content)
|
||||
|
||||
def __del__(self) -> None:
|
||||
self.proc.terminate()
|
||||
self.proc.communicate()
|
||||
|
||||
|
||||
class Js2PyInterpreter(Interpreter):
|
||||
|
@ -128,7 +166,7 @@ class Js2PyInterpreter(Interpreter):
|
|||
|
||||
def atob(s: str) -> str:
|
||||
"""Wrapper for the built-in library function.
|
||||
Decodes base64 string
|
||||
Decodes a base64 string
|
||||
|
||||
Args:
|
||||
s (str): Encoded data
|
||||
|
@ -140,8 +178,26 @@ def atob(s: str) -> str:
|
|||
return base64.standard_b64decode(str(s)).decode('utf-8')
|
||||
|
||||
|
||||
def get_interpreter(create: Type[Interpreter] = Js2PyInterpreter) -> 'Interpreter':
|
||||
def get_interpreter(
|
||||
create: Type[Interpreter] = Js2PyInterpreter,
|
||||
*args, **kwargs) -> 'Interpreter':
|
||||
"""Get or create a JS interpreter.
|
||||
`*args` and `**kwargs` will be passed
|
||||
directly to JS interpreter `__init__`
|
||||
(when creating it)
|
||||
|
||||
Args:
|
||||
create (Type[Interpreter], optional): Preferred interpreter
|
||||
|
||||
Returns:
|
||||
JS interpreter instance
|
||||
"""
|
||||
|
||||
global js
|
||||
|
||||
# create if none
|
||||
if js is None:
|
||||
js = create()
|
||||
js = create(*args, **kwargs)
|
||||
|
||||
# and return
|
||||
return js
|
||||
|
|
30
python_aternos/data/server.js
Normal file
30
python_aternos/data/server.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
const http = require('http')
|
||||
const process = require('process')
|
||||
|
||||
args = process.argv.slice(2)
|
||||
|
||||
const port = args[0] || 8000
|
||||
const host = args[1] || 'localhost'
|
||||
|
||||
const listener = (req, res) => {
|
||||
|
||||
if (req.method != 'POST')
|
||||
res.writeHead(405) & res.end()
|
||||
|
||||
let body = ''
|
||||
req.on('data', chunk => (body += chunk))
|
||||
|
||||
req.on('end', () => {
|
||||
let resp
|
||||
try { resp = JSON.stringify(eval(body)) }
|
||||
catch (ex) { resp = ex.message }
|
||||
res.writeHead(200)
|
||||
res.end(resp)
|
||||
})
|
||||
}
|
||||
|
||||
window = global
|
||||
document = window.document || {}
|
||||
|
||||
const server = http.createServer(listener)
|
||||
server.listen(port, host)
|
1
setup.py
1
setup.py
|
@ -42,4 +42,5 @@ setuptools.setup(
|
|||
],
|
||||
packages=['python_aternos'],
|
||||
python_requires=">=3.7",
|
||||
include_package_data=True,
|
||||
)
|
||||
|
|
|
@ -51,10 +51,10 @@ class TestJs2Py(unittest.TestCase):
|
|||
|
||||
def test_exec(self) -> None:
|
||||
|
||||
for i, f in enumerate(self.tests):
|
||||
self.js.exec_js(f)
|
||||
for func, exp in zip(self.tests, self.results):
|
||||
self.js.exec_js(func)
|
||||
res = self.js['AJAX_TOKEN']
|
||||
self.assertEqual(res, self.results[i])
|
||||
self.assertEqual(res, exp)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
24
tests/test_jsnode.py
Normal file
24
tests/test_jsnode.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import unittest
|
||||
|
||||
from python_aternos import atjsparse
|
||||
from tests import files
|
||||
|
||||
|
||||
class TestJsNode(unittest.TestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
|
||||
self.tests = files.read_sample('token_input.txt')
|
||||
self.results = files.read_sample('token_output.txt')
|
||||
self.js = atjsparse.NodeInterpreter()
|
||||
|
||||
def test_exec(self) -> None:
|
||||
|
||||
for func, exp in zip(self.tests, self.results):
|
||||
self.js.exec_js(func)
|
||||
res = self.js['AJAX_TOKEN']
|
||||
self.assertEqual(res, exp)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Reference in a new issue