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# -*- coding: utf-8 -*-
2"""
3@file
4@brief Helpers around JSON
5"""
6import uuid
7import os
8import shutil
9import urllib.request as liburl
10import urllib.error as liberror
11from IPython.display import display_html, display_javascript
14class UrlNotFoundError(Exception):
15 """
16 Raised when a url does not exist.
17 """
19 def __init__(self, url, code):
20 Exception.__init__(
21 self, "Url not found: returned code={0} for '{1}'".format(code, url))
24class JavascriptScriptError(ValueError):
25 """
26 Raised when the class does not find what it expects.
27 """
28 pass
31def check_url(url):
32 "Checks urls."
33 try:
34 liburl.urlopen(url) # pylint: disable=R1732
35 return True
36 except liberror.HTTPError as e:
37 raise UrlNotFoundError(url, e.code) from e
38 except liberror.URLError as e:
39 raise UrlNotFoundError(url, e.reason) from e
40 except Exception as e:
41 raise Exception("Issue with url '{0}'".format(url)) from e
44class RenderJSRaw:
45 """
46 Adds :epkg:`javascript` into a noteboook.
47 """
49 def __init__(self, script, width="100%", height="100%", divid=None, css=None,
50 libs=None, style=None, only_html=False, div_class=None, check_urls=True,
51 local=False):
52 """
53 @param script (str) script
54 @param width (str) width
55 @param height (str) height
56 @param style (str) style (added in ``<style>...</style>``)
57 @param divid (str|None) id of the div
58 @param css (list) list of css
59 @param libs (list) list of dependencies
60 @param only_html (bool) use only function
61 `display_html <http://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html?
62 highlight=display_html#IPython.display.display_html>`_
63 and not `display_javascript
64 <http://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html?
65 highlight=display_html#IPython.display.display_javascript>`_ to add
66 javascript to the page.
67 @param div_class (str) class of the section ``div`` which will host the results
68 of the javascript
69 @param check_urls (bool) by default, check url exists
70 @param local (bool|False) use local javascript files
71 """
72 self.script = script
73 self.uuid = divid if divid else "M" + \
74 str(uuid.uuid4()).replace("-", "")
75 if style is None:
76 style = ''
77 if width is not None and 'width' not in style:
78 style += "width:{0};".format(width)
79 if height is not None and 'height' not in style:
80 style += "height:{0};".format(height)
81 if not style:
82 style = None
83 else:
84 if width is not None and 'width' not in style:
85 style += "width:{0};".format(width)
86 if height is not None and 'height' not in style:
87 style += "height:{0};".format(height)
88 self.style = style
89 self.only_html = only_html
90 self.div_class = div_class
91 if "__ID__" not in script:
92 raise JavascriptScriptError(
93 "The sript does not contain any string __ID__. It is replaced by the ID value in script:\n{0}".format(script))
94 self.local = local
95 self.css, self.libs = self._copy_local(css, libs, local)
96 if check_urls and not local:
97 if self.css is not None:
98 for c in self.css:
99 check_url(c)
100 if self.libs is not None:
101 for lib in self.libs:
102 if isinstance(lib, dict):
103 check_url(lib['path'])
104 else:
105 check_url(lib)
107 def _copy_local(self, css, libs, local):
108 """
109 If *self.local*, copies javascript dependencies in the local folder.
111 @param css list of css
112 @param libs list of libraries
113 @param local boolean or new location
114 @return tuple (css, libs)
115 """
116 if not self.local:
117 return css, libs
118 to_copy = []
119 if css:
120 to_copy.extend(css)
121 if libs:
122 for js in libs:
123 if isinstance(js, dict):
124 to_copy.append(js['path'])
125 else:
126 to_copy.append(js)
128 for js in to_copy:
129 if not os.path.exists(js):
130 raise FileNotFoundError("Unable to find '{0}'".format(js))
131 dest = local if isinstance(local, str) else os.getcwd()
132 shutil.copy(js, dest)
134 if css:
135 css = [os.path.split(c)[-1] for c in css]
136 if libs:
137 def proc(d):
138 "proc"
139 if isinstance(d, dict):
140 d = d.copy()
141 d['path'] = os.path.split(d['path'])[-1]
142 return d
143 else:
144 return os.path.split(d)[-1]
145 libs = [proc(c) for c in libs]
146 return css, libs
148 def generate_html(self):
149 """
150 Overloads method
151 `_ipython_display_ <http://ipython.readthedocs.io/en/stable/config/integrating.html?highlight=Integrating%20>`_.
153 @return `HTML <http://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#IPython.display.HTML>`_ text,
154 `Javascript <http://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#IPython.display.Javascript>`_ text
155 """
156 if self.style:
157 style = ' style="{0}"'.format(self.style)
158 else:
159 style = ""
160 if self.div_class:
161 divcl = ' class="{0}"'.format(self.div_class)
162 else:
163 divcl = ""
164 if self.css:
165 css = "".join(
166 '<link rel="stylesheet" href="{0}" type="text/css" />'.format(c) for c in self.css)
167 ht = '<div id="{uuid}-css">{css}<div{divcl} id="{uuid}"{style}></div></div>'.format(
168 uuid=self.uuid, css=css, style=style, divcl=divcl)
169 else:
170 ht = '<div id="{uuid}-cont"><div{divcl} id="{uuid}"{style}></div></div>'.format(
171 uuid=self.uuid, style=style, divcl=divcl)
173 script = self.script.replace("__ID__", self.uuid)
174 if self.libs:
175 names = []
176 paths = []
177 shims = {}
178 args = []
179 exports = []
180 for lib in self.libs:
181 if isinstance(lib, dict):
182 name = lib.get("name", None)
183 if "path" in lib:
184 p = lib["path"]
185 if name is None:
186 name = ".".join((p.split("/")[-1]).split(".")[:-1])
187 path = ".".join(p.split(".")[:-1])
188 paths.append((name, path))
189 else:
190 raise KeyError(
191 "unable to find 'path' in {0}".format(lib))
192 names.append(name)
193 args.append(name)
194 if "exports" in lib:
195 if name not in shims:
196 shims[name] = {}
197 shims[name]["exports"] = lib["exports"]
198 if isinstance(lib["exports"], list):
199 exports.extend(lib["exports"])
200 else:
201 exports.append(lib["exports"])
202 if "deps" in lib:
203 if name not in shims:
204 shims[name] = {}
205 shims[name]["deps"] = lib["deps"]
206 else:
207 names.append(lib)
208 if len(names) == 0:
209 raise ValueError(
210 "names is empty.\nlibs={0}\npaths={1}\nshims={2}\nexports={3}".format(
211 self.libs, paths, shims, exports))
212 require = ",".join("'{0}'".format(na) for na in names)
214 config = ["require.config({"]
215 if len(paths) > 0:
216 config.append("paths:{")
217 for name, path in paths:
218 config.append("'{0}':'{1}',".format(name, path))
219 config.append("},")
220 if len(shims) > 0:
221 config.append("shim:{")
223 def vd(d):
224 "vd"
225 rows = []
226 for k, v in sorted(d.items()):
227 rows.append("'{0}':{1}".format(
228 k, v if isinstance(v, list) else "'{0}'".format(v)))
229 return "{%s}" % ",".join(rows)
230 for k, v in sorted(shims.items()):
231 config.append("'{0}':{1},".format(k, vd(v)))
232 config.append("},")
233 config.append("});")
234 if len(config) > 2:
235 prefix = "\n".join(config) + "\n"
236 else:
237 prefix = ""
238 js = prefix + \
239 """\nrequire([%s], function(%s) { %s });\n""" % (
240 require, ",".join(args), script)
241 else:
242 js = script
243 if self.only_html:
244 ht += "\n<script>\n%s\n</script>" % js
245 return ht, None
246 return ht, js
249class RenderJSObj(RenderJSRaw):
250 """
251 Renders JS using :epkg:`javascript`.
252 """
254 def _ipython_display_(self):
255 """
256 overloads method
257 `_ipython_display_ <http://ipython.readthedocs.io/en/stable/config/integrating.html?highlight=Integrating%20>`_.
258 """
259 ht, js = self.generate_html()
260 if js is None:
261 display_html(ht, raw=True)
262 else:
263 display_html(ht, raw=True)
264 display_javascript(js, raw=True)
267class RenderJS(RenderJSRaw):
268 """
269 Renders :epkg:`javascript`, only outputs :epkg:`HTML`.
270 """
272 def _repr_html_(self):
273 """
274 Overloads method *_repr_html_*.
275 """
276 ht, js = self.generate_html()
277 if js is not None:
278 ht += "\n<script>\n{0}\n</script>\n".format(js)
279 return ht