Web server for node.js interpreter, added docstrings, unittest

This commit is contained in:
DarkCat09 2022-12-25 17:45:22 +04:00
parent a770df7334
commit d36a0528ea
5 changed files with 127 additions and 16 deletions

View file

@ -1,15 +1,20 @@
"""Parsing and executing JavaScript code""" """Parsing and executing JavaScript code"""
import abc import abc
import json
import base64 import base64
import time
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from typing import Optional, Union, Any from typing import Optional, Union
from typing import Type from typing import Type, Any
import regex import regex
import js2py import js2py
import requests
js: Optional['Interpreter'] = None js: Optional['Interpreter'] = None
@ -18,9 +23,19 @@ class Interpreter(abc.ABC):
"""Base JS interpreter class""" """Base JS interpreter class"""
def __init__(self) -> None: def __init__(self) -> None:
"""Base JS interpreter class"""
pass pass
def __getitem__(self, name: str) -> Any: 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) return self.get_var(name)
@abc.abstractmethod @abc.abstractmethod
@ -48,24 +63,47 @@ class Interpreter(abc.ABC):
class NodeInterpreter(Interpreter): 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__() 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( self.proc = subprocess.Popen(
node, args=[
stdout=subprocess.PIPE, node, server_js,
f'{port}', host,
],
) )
time.sleep(0.1)
def exec_js(self, func: str) -> None: 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: def get_var(self, name: str) -> Any:
assert self.proc.stdout is not None resp = requests.post(self.url, data=name)
self.proc.stdout.read() resp.raise_for_status()
self.proc.communicate(name.encode('utf-8')) return json.loads(resp.content)
return self.proc.stdout.read().decode('utf-8')
def __del__(self) -> None: def __del__(self) -> None:
self.proc.terminate() self.proc.terminate()
self.proc.communicate()
class Js2PyInterpreter(Interpreter): class Js2PyInterpreter(Interpreter):
@ -128,7 +166,7 @@ class Js2PyInterpreter(Interpreter):
def atob(s: str) -> str: def atob(s: str) -> str:
"""Wrapper for the built-in library function. """Wrapper for the built-in library function.
Decodes base64 string Decodes a base64 string
Args: Args:
s (str): Encoded data s (str): Encoded data
@ -140,8 +178,26 @@ def atob(s: str) -> str:
return base64.standard_b64decode(str(s)).decode('utf-8') 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 global js
# create if none
if js is None: if js is None:
js = create() js = create(*args, **kwargs)
# and return
return js return js

View 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)

View file

@ -42,4 +42,5 @@ setuptools.setup(
], ],
packages=['python_aternos'], packages=['python_aternos'],
python_requires=">=3.7", python_requires=">=3.7",
include_package_data=True,
) )

View file

@ -51,10 +51,10 @@ class TestJs2Py(unittest.TestCase):
def test_exec(self) -> None: def test_exec(self) -> None:
for i, f in enumerate(self.tests): for func, exp in zip(self.tests, self.results):
self.js.exec_js(f) self.js.exec_js(func)
res = self.js['AJAX_TOKEN'] res = self.js['AJAX_TOKEN']
self.assertEqual(res, self.results[i]) self.assertEqual(res, exp)
if __name__ == '__main__': if __name__ == '__main__':

24
tests/test_jsnode.py Normal file
View 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()