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:
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
14class NodeJsException(Exception):
15 """
16 Raised if :epkg:`node.js` fails.
17 """
18 pass
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.
27 @param script :epkg:`javascript`
28 @param required required libraries (does not guaranteed to work)
29 @return :epkg:`python` function
31 The module relies on :epkg:`js2py` and :epkg:`node.js`.
32 Dependencies must be installed with :epkg:`npm`:.
34 ::
36 npm install babel-core babel-cli babel-preset-es2015 babel-polyfill babelify browserify babel-preset-env
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
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.
58 @param dest installation folder
59 @param module_list list of modules to install
60 @param fLOG logging function
62 If *module_list is None*, it is replaced by:
64 ::
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)))
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
101def run_js_with_nodejs(script, path_dependencies=None, fLOG=noLOG):
102 """
103 Runs a :epkg:`javascript` script with :epkg:`node.js`.
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
126_require_cache = {}
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>`_.
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
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
153 py_name = module_name.replace('-', '_')
154 module_filename = '%s.py' % py_name
155 full_name = os.path.join(cache_folder, module_filename)
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))
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)
169 with open(in_file_name, 'w', encoding='utf-8') as f:
170 f.write(code)
172 pkg_name = module_name.partition('/')[0]
173 install_node_js_modules(cache_folder, [pkg_name], fLOG=fLOG)
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)
183 with open(out_file_name, "r", encoding="utf-8") as f:
184 js_code = f.read()
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)
192 with open(full_name, 'w', encoding="utf-8") as f:
193 f.write(py_code)
195 _require_cache[module_name] = py_code
197 context = {}
198 exec(py_code, context)
199 return context['var'][var_name].to_py()