Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2@file 

3@brief Helpers around images and :epkg:`javascript`. 

4See also: 

5 

6* `pyduktape <https://github.com/stefano/pyduktape>`_ 

7* `Python Mini Racer <https://github.com/sqreen/PyMiniRacer>`_ 

8* `python-requirejs <https://github.com/wq/python-requirejs>`_ 

9""" 

10import os 

11from ..loghelper import run_cmd, noLOG 

12 

13 

14class NodeJsException(Exception): 

15 """ 

16 Raised if :epkg:`node.js` fails. 

17 """ 

18 pass 

19 

20 

21def run_js_fct(script, required=None): 

22 """ 

23 Assuming *script* contains some :epkg:`javascript` 

24 which produces :epkg:`SVG`. This functions runs 

25 the code. 

26 

27 @param script :epkg:`javascript` 

28 @param required required libraries (does not guaranteed to work) 

29 @return :epkg:`python` function 

30 

31 The module relies on :epkg:`js2py` and :epkg:`node.js`. 

32 Dependencies must be installed with :epkg:`npm`:. 

33 

34 :: 

35 

36 npm install babel-core babel-cli babel-preset-es2015 babel-polyfill babelify browserify babel-preset-env 

37 

38 Function @see fn install_node_js_modules can be run with admin right for that. 

39 :epkg:`js2py` tries to convert a dependency into :epkg:`Python` 

40 """ 

41 from js2py import eval_js, require, node_import # pylint: disable=W0621 

42 # To skip npm installation. 

43 node_import.DID_INIT = True 

44 if required: 

45 if not isinstance(required, list): 

46 required = [required] 

47 for r in required: 

48 require(r) 

49 fct = eval_js(script) 

50 return fct 

51 

52 

53def install_node_js_modules(dest, module_list=None, fLOG=noLOG): 

54 """ 

55 Installs missing dependencies to compile a convert a :epkg:`javascript` 

56 libraries. 

57 

58 @param dest installation folder 

59 @param module_list list of modules to install 

60 @param fLOG logging function 

61 

62 If *module_list is None*, it is replaced by: 

63 

64 :: 

65 

66 ['babel-core', 'babel-cli', 'babel-preset-env', 

67 'babel-polyfill', 'babelify', 'browserify', 

68 'babel-preset-es2015'] 

69 """ 

70 if module_list is None: 

71 module_list = ['babel-core', 'babel-cli', 'babel-preset-env', 

72 'babel-polyfill', 'babelify', 'browserify', 

73 'babel-preset-es2015'] 

74 dir_name = dest 

75 node_modules = os.path.join(dir_name, "node_modules") 

76 should = [os.path.join(node_modules, n) for n in module_list] 

77 if any(map(lambda x: not os.path.exists(x), should)): 

78 cmds = ['npm install ' + ' '.join(module_list)] 

79 errs = [] 

80 for cmd in cmds: 

81 fLOG("[install_node_js_modules] run ", cmd) 

82 err = run_cmd(cmd, wait=True, change_path=dir_name, fLOG=fLOG)[1] 

83 errs.append(err) 

84 if not os.path.exists(node_modules): 

85 raise RuntimeError( # pragma: no cover 

86 "Unable to run from '{0}' commands line:\n{1}\n--due to--\n{2}".format( 

87 dir_name, "\n".join(cmds), "\n".join(errs))) 

88 

89 

90def nodejs_version(): 

91 """ 

92 Returns :epkg:`node.js` version. 

93 """ 

94 out, err = run_cmd('node -v', wait=True) 

95 if len(err) > 0: 

96 raise NodeJsException( # pragma: no cover 

97 "Unable to find node\n{0}".format(err)) 

98 return out 

99 

100 

101def run_js_with_nodejs(script, path_dependencies=None, fLOG=noLOG): 

102 """ 

103 Runs a :epkg:`javascript` script with :epkg:`node.js`. 

104 

105 @param script script to run 

106 @param path_dependencies where dependencies can be found if needed 

107 @param fLOG logging function 

108 @return output of the script 

109 """ 

110 script_clean = script.replace("\"", "\\\"").replace("\n", " ") 

111 cmd = 'node -e "{0}"'.format(script_clean) 

112 out, err = run_cmd(cmd, change_path=path_dependencies, 

113 fLOG=fLOG, wait=True) 

114 if len(err) > 0: 

115 filtered = "\n".join(_ for _ in err.split('\n') 

116 if not _.startswith("[BABEL] Note:")) 

117 else: 

118 filtered = err 

119 if len(filtered) > 0: 

120 raise NodeJsException( # pragma: no cover 

121 "Execution of node.js failed.\n--CMD--\n{0}\n--ERR--\n{1}\n--OUT--\n{2}\n" 

122 "--SCRIPT--\n{3}".format(cmd, err, out, script)) 

123 return out 

124 

125 

126_require_cache = {} 

127 

128 

129def require(module_name, cache_folder='.', suffix='_pyq', update=False, fLOG=noLOG): 

130 """ 

131 Modified version of function *require* in 

132 `node_import.py <https://github.com/PiotrDabkowski/Js2Py/blob/master/js2py/node_import.py>`_. 

133 

134 @param module_name required library name 

135 @param cache_folder location of the files the function creates 

136 @param suffix change the suffix if you use the same folder for multiple files 

137 @param update update the converted script 

138 @param fLOG logging function 

139 @return outcome of the javascript script 

140 

141 The function is not fully tested. 

142 """ 

143 if module_name.endswith('.js'): 

144 raise ValueError( # pragma: no cover 

145 "module_name must the name without extension .js") 

146 global _require_cache 

147 if module_name in _require_cache and not update: 

148 py_code = _require_cache[module_name] 

149 else: 

150 from js2py.node_import import ADD_TO_GLOBALS_FUNC, GET_FROM_GLOBALS_FUNC 

151 from js2py import translate_js 

152 

153 py_name = module_name.replace('-', '_') 

154 module_filename = '%s.py' % py_name 

155 full_name = os.path.join(cache_folder, module_filename) 

156 

157 var_name = py_name.rpartition('/')[-1] 

158 in_file_name = os.path.join( 

159 cache_folder, "require_{0}_in{1}.js".format(module_name, suffix)) 

160 out_file_name = os.path.join( 

161 cache_folder, "require_{0}_out{1}.js".format(module_name, suffix)) 

162 

163 code = ADD_TO_GLOBALS_FUNC 

164 code += """ 

165 var module_temp_love_python = require('{0}'); 

166 addToGlobals('{0}', module_temp_love_python); 

167 """.format(module_name) 

168 

169 with open(in_file_name, 'w', encoding='utf-8') as f: 

170 f.write(code) 

171 

172 pkg_name = module_name.partition('/')[0] 

173 install_node_js_modules(cache_folder, [pkg_name], fLOG=fLOG) 

174 

175 inline_script = "(require('browserify')('%s').bundle(function (err,data)" + \ 

176 "{fs.writeFile('%s',require('babel-core').transform(data," + \ 

177 "{'presets':require('babel-preset-es2015')}).code,()=>{});}))" 

178 inline_script = inline_script % (in_file_name.replace("\\", "\\\\"), 

179 out_file_name.replace("\\", "\\\\")) 

180 run_js_with_nodejs(inline_script, fLOG=fLOG, 

181 path_dependencies=cache_folder) 

182 

183 with open(out_file_name, "r", encoding="utf-8") as f: 

184 js_code = f.read() 

185 

186 js_code += GET_FROM_GLOBALS_FUNC 

187 js_code += ";var {0} = getFromGlobals('{1}');{0}".format( 

188 var_name, module_name) 

189 fLOG('[require] translating', out_file_name) 

190 py_code = translate_js(js_code) 

191 

192 with open(full_name, 'w', encoding="utf-8") as f: 

193 f.write(py_code) 

194 

195 _require_cache[module_name] = py_code 

196 

197 context = {} 

198 exec(py_code, context) 

199 return context['var'][var_name].to_py()