Source code for sensai.util.jscode
"""
Utility classes and functions for JavaScript code generation
"""
import json
from abc import abstractmethod, ABC
from typing import Union, Any
import numpy as np
from .string import list_string
PythonType = Union[str, int, bool, float]
[docs]class JsCode(ABC):
def __str__(self):
return self.get_js_code()
[docs] @abstractmethod
def get_js_code(self):
pass
[docs]class JsCodeLiteral(JsCode):
def __init__(self, js_code: str):
self.js_code = js_code
[docs] def get_js_code(self):
return self.js_code
[docs]class JsValue(JsCode, ABC):
[docs] @classmethod
def from_python(cls, value: PythonType):
t = type(value)
if t == str:
return cls.string_value(value)
elif t == int:
return cls.int_value(value)
elif value is None:
return cls.undefined()
elif t == bool:
return cls.bool_value(value)
elif t in (float, np.float64, np.float):
return cls.float_value(value)
else:
raise ValueError(f"Unsupported value of type {type(value)}: {value}")
[docs] @classmethod
def from_value(cls, value: Union["JsValue", PythonType]):
if isinstance(value, JsValue):
return value
else:
return cls.from_python(value)
[docs] def is_undefined(self):
return self.get_js_code() == "undefined"
[docs] @staticmethod
def string_value(s: str):
s = s.replace('"', r'\"')
return JsValueLiteral(f'"{s}"')
[docs] @staticmethod
def int_value(value: int):
return JsValueLiteral(str(int(value)))
[docs] @staticmethod
def float_value(value: Union[float, int]):
return JsValueLiteral(str(float(value)))
[docs] @staticmethod
def bool_value(value: bool):
b = bool(value)
return JsValueLiteral("true" if b else "false")
[docs] @staticmethod
def undefined():
return JsValueLiteral("undefined")
[docs] @staticmethod
def null():
return JsValueLiteral("null")
[docs]class JsValueLiteral(JsValue):
def __init__(self, js_code: str):
self.js_code = js_code
[docs] def get_js_code(self):
return self.js_code
[docs]def js_value(value: Union[JsValue, PythonType]) -> JsValue:
return JsValue.from_value(value)
[docs]def js_arg_list(*args: Union[JsValue, PythonType], drop_trailing_undefined=True) -> JsCode:
"""
:param args: arguments that are either JsValue instances or (supported) Python values
:param drop_trailing_undefined: whether to drop trailing arguments that are undefined/None
:return: the JsCode
"""
args = [js_value(a) for a in args]
last_index_to_include = len(args) - 1
if drop_trailing_undefined:
while last_index_to_include >= 0 and args[last_index_to_include].is_undefined():
last_index_to_include -= 1
args = args[:last_index_to_include+1]
return JsCodeLiteral(", ".join(map(str, args)))
[docs]class JsObject(JsValue):
def __init__(self):
self.data = {}
[docs] def add(self, key: str, value: Union[JsValue, PythonType]):
self.data[key] = js_value(value)
[docs] def add_string(self, key: str, value: str):
self.data[key] = JsValue.string_value(value)
[docs] def add_code_literal(self, key: str, value: str):
self.data[key] = value
[docs] def add_float(self, key: str, value: Union[float, int]):
self.data[key] = JsValue.float_value(value)
[docs] def add_json(self, key: str, value: Any):
"""
:param key: key within the object
:param value: any Python object which can be converted to JSON
"""
self.add_code_literal(key, json.dumps(value))
[docs] def get_js_code(self):
return "{" + ", ".join(f'"{k}": {v}' for k, v in self.data.items()) + "}"
def __len__(self):
return len(self.data)
[docs]class JsClassInstance(JsValueLiteral):
def __init__(self, class_name, *args: Union[JsValue, PythonType]):
arg_list = js_arg_list(*args, drop_trailing_undefined=False)
super().__init__(f"new {class_name}({arg_list})")
[docs]class JsList(JsValueLiteral):
def __init__(self, *values: Union[JsValue, PythonType]):
super().__init__(list_string([js_value(x) for x in values]))