Coverage for src/sensai/util/jscode.py: 0%

98 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-13 22:17 +0000

1""" 

2Utility classes and functions for JavaScript code generation 

3""" 

4import json 

5from abc import abstractmethod, ABC 

6from typing import Union, Any 

7 

8import numpy as np 

9 

10from .string import list_string 

11 

12 

13PythonType = Union[str, int, bool, float] 

14 

15 

16class JsCode(ABC): 

17 def __str__(self): 

18 return self.get_js_code() 

19 

20 @abstractmethod 

21 def get_js_code(self): 

22 pass 

23 

24 

25class JsCodeLiteral(JsCode): 

26 def __init__(self, js_code: str): 

27 self.js_code = js_code 

28 

29 def get_js_code(self): 

30 return self.js_code 

31 

32 

33class JsValue(JsCode, ABC): 

34 @classmethod 

35 def from_python(cls, value: PythonType): 

36 t = type(value) 

37 if t == str: 

38 return cls.string_value(value) 

39 elif t == int: 

40 return cls.int_value(value) 

41 elif value is None: 

42 return cls.undefined() 

43 elif t == bool: 

44 return cls.bool_value(value) 

45 elif t in (float, np.float64, np.float): 

46 return cls.float_value(value) 

47 else: 

48 raise ValueError(f"Unsupported value of type {type(value)}: {value}") 

49 

50 @classmethod 

51 def from_value(cls, value: Union["JsValue", PythonType]): 

52 if isinstance(value, JsValue): 

53 return value 

54 else: 

55 return cls.from_python(value) 

56 

57 def is_undefined(self): 

58 return self.get_js_code() == "undefined" 

59 

60 @staticmethod 

61 def string_value(s: str): 

62 s = s.replace('"', r'\"') 

63 return JsValueLiteral(f'"{s}"') 

64 

65 @staticmethod 

66 def int_value(value: int): 

67 return JsValueLiteral(str(int(value))) 

68 

69 @staticmethod 

70 def float_value(value: Union[float, int]): 

71 return JsValueLiteral(str(float(value))) 

72 

73 @staticmethod 

74 def bool_value(value: bool): 

75 b = bool(value) 

76 return JsValueLiteral("true" if b else "false") 

77 

78 @staticmethod 

79 def undefined(): 

80 return JsValueLiteral("undefined") 

81 

82 @staticmethod 

83 def null(): 

84 return JsValueLiteral("null") 

85 

86 

87class JsValueLiteral(JsValue): 

88 def __init__(self, js_code: str): 

89 self.js_code = js_code 

90 

91 def get_js_code(self): 

92 return self.js_code 

93 

94 

95def js_value(value: Union[JsValue, PythonType]) -> JsValue: 

96 return JsValue.from_value(value) 

97 

98 

99def js_arg_list(*args: Union[JsValue, PythonType], drop_trailing_undefined=True) -> JsCode: 

100 """ 

101 :param args: arguments that are either JsValue instances or (supported) Python values 

102 :param drop_trailing_undefined: whether to drop trailing arguments that are undefined/None 

103 :return: the JsCode 

104 """ 

105 args = [js_value(a) for a in args] 

106 last_index_to_include = len(args) - 1 

107 if drop_trailing_undefined: 

108 while last_index_to_include >= 0 and args[last_index_to_include].is_undefined(): 

109 last_index_to_include -= 1 

110 args = args[:last_index_to_include+1] 

111 return JsCodeLiteral(", ".join(map(str, args))) 

112 

113 

114class JsObject(JsValue): 

115 def __init__(self): 

116 self.data = {} 

117 

118 def add(self, key: str, value: Union[JsValue, PythonType]): 

119 self.data[key] = js_value(value) 

120 

121 def add_string(self, key: str, value: str): 

122 self.data[key] = JsValue.string_value(value) 

123 

124 def add_code_literal(self, key: str, value: str): 

125 self.data[key] = value 

126 

127 def add_float(self, key: str, value: Union[float, int]): 

128 self.data[key] = JsValue.float_value(value) 

129 

130 def add_json(self, key: str, value: Any): 

131 """ 

132 :param key: key within the object 

133 :param value: any Python object which can be converted to JSON 

134 """ 

135 self.add_code_literal(key, json.dumps(value)) 

136 

137 def get_js_code(self): 

138 return "{" + ", ".join(f'"{k}": {v}' for k, v in self.data.items()) + "}" 

139 

140 def __len__(self): 

141 return len(self.data) 

142 

143 

144class JsClassInstance(JsValueLiteral): 

145 def __init__(self, class_name, *args: Union[JsValue, PythonType]): 

146 arg_list = js_arg_list(*args, drop_trailing_undefined=False) 

147 super().__init__(f"new {class_name}({arg_list})") 

148 

149 

150class JsList(JsValueLiteral): 

151 def __init__(self, *values: Union[JsValue, PythonType]): 

152 super().__init__(list_string([js_value(x) for x in values]))