##// END OF EJS Templates
make element_id kwarg only.
Matthias Bussonnier -
Show More
@@ -1,643 +1,643 b''
1 """Various display related classes.
1 """Various display related classes.
2
2
3 Authors : MinRK, gregcaporaso, dannystaple
3 Authors : MinRK, gregcaporaso, dannystaple
4 """
4 """
5 from html import escape as html_escape
5 from html import escape as html_escape
6 from os.path import exists, isfile, splitext, abspath, join, isdir
6 from os.path import exists, isfile, splitext, abspath, join, isdir
7 from os import walk, sep, fsdecode
7 from os import walk, sep, fsdecode
8
8
9 from IPython.core.display import DisplayObject, TextDisplayObject
9 from IPython.core.display import DisplayObject, TextDisplayObject
10
10
11 __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
11 __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
12 'FileLink', 'FileLinks', 'Code']
12 'FileLink', 'FileLinks', 'Code']
13
13
14
14
15 class Audio(DisplayObject):
15 class Audio(DisplayObject):
16 """Create an audio object.
16 """Create an audio object.
17
17
18 When this object is returned by an input cell or passed to the
18 When this object is returned by an input cell or passed to the
19 display function, it will result in Audio controls being displayed
19 display function, it will result in Audio controls being displayed
20 in the frontend (only works in the notebook).
20 in the frontend (only works in the notebook).
21
21
22 Parameters
22 Parameters
23 ----------
23 ----------
24 data : numpy array, list, unicode, str or bytes
24 data : numpy array, list, unicode, str or bytes
25 Can be one of
25 Can be one of
26
26
27 * Numpy 1d array containing the desired waveform (mono)
27 * Numpy 1d array containing the desired waveform (mono)
28 * Numpy 2d array containing waveforms for each channel.
28 * Numpy 2d array containing waveforms for each channel.
29 Shape=(NCHAN, NSAMPLES). For the standard channel order, see
29 Shape=(NCHAN, NSAMPLES). For the standard channel order, see
30 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
30 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
31 * List of float or integer representing the waveform (mono)
31 * List of float or integer representing the waveform (mono)
32 * String containing the filename
32 * String containing the filename
33 * Bytestring containing raw PCM data or
33 * Bytestring containing raw PCM data or
34 * URL pointing to a file on the web.
34 * URL pointing to a file on the web.
35
35
36 If the array option is used, the waveform will be normalized.
36 If the array option is used, the waveform will be normalized.
37
37
38 If a filename or url is used, the format support will be browser
38 If a filename or url is used, the format support will be browser
39 dependent.
39 dependent.
40 url : unicode
40 url : unicode
41 A URL to download the data from.
41 A URL to download the data from.
42 filename : unicode
42 filename : unicode
43 Path to a local file to load the data from.
43 Path to a local file to load the data from.
44 embed : boolean
44 embed : boolean
45 Should the audio data be embedded using a data URI (True) or should
45 Should the audio data be embedded using a data URI (True) or should
46 the original source be referenced. Set this to True if you want the
46 the original source be referenced. Set this to True if you want the
47 audio to playable later with no internet connection in the notebook.
47 audio to playable later with no internet connection in the notebook.
48
48
49 Default is `True`, unless the keyword argument `url` is set, then
49 Default is `True`, unless the keyword argument `url` is set, then
50 default value is `False`.
50 default value is `False`.
51 rate : integer
51 rate : integer
52 The sampling rate of the raw data.
52 The sampling rate of the raw data.
53 Only required when data parameter is being used as an array
53 Only required when data parameter is being used as an array
54 autoplay : bool
54 autoplay : bool
55 Set to True if the audio should immediately start playing.
55 Set to True if the audio should immediately start playing.
56 Default is `False`.
56 Default is `False`.
57 normalize : bool
57 normalize : bool
58 Whether audio should be normalized (rescaled) to the maximum possible
58 Whether audio should be normalized (rescaled) to the maximum possible
59 range. Default is `True`. When set to `False`, `data` must be between
59 range. Default is `True`. When set to `False`, `data` must be between
60 -1 and 1 (inclusive), otherwise an error is raised.
60 -1 and 1 (inclusive), otherwise an error is raised.
61 Applies only when `data` is a list or array of samples; other types of
61 Applies only when `data` is a list or array of samples; other types of
62 audio are never normalized.
62 audio are never normalized.
63
63
64 Examples
64 Examples
65 --------
65 --------
66 ::
66 ::
67
67
68 # Generate a sound
68 # Generate a sound
69 import numpy as np
69 import numpy as np
70 framerate = 44100
70 framerate = 44100
71 t = np.linspace(0,5,framerate*5)
71 t = np.linspace(0,5,framerate*5)
72 data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)
72 data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)
73 Audio(data,rate=framerate)
73 Audio(data,rate=framerate)
74
74
75 # Can also do stereo or more channels
75 # Can also do stereo or more channels
76 dataleft = np.sin(2*np.pi*220*t)
76 dataleft = np.sin(2*np.pi*220*t)
77 dataright = np.sin(2*np.pi*224*t)
77 dataright = np.sin(2*np.pi*224*t)
78 Audio([dataleft, dataright],rate=framerate)
78 Audio([dataleft, dataright],rate=framerate)
79
79
80 Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # From URL
80 Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # From URL
81 Audio(url="http://www.w3schools.com/html/horse.ogg")
81 Audio(url="http://www.w3schools.com/html/horse.ogg")
82
82
83 Audio('/path/to/sound.wav') # From file
83 Audio('/path/to/sound.wav') # From file
84 Audio(filename='/path/to/sound.ogg')
84 Audio(filename='/path/to/sound.ogg')
85
85
86 Audio(b'RAW_WAV_DATA..) # From bytes
86 Audio(b'RAW_WAV_DATA..) # From bytes
87 Audio(data=b'RAW_WAV_DATA..)
87 Audio(data=b'RAW_WAV_DATA..)
88
88
89 """
89 """
90 _read_flags = 'rb'
90 _read_flags = 'rb'
91
91
92 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True,
92 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *,
93 element_id=None):
93 element_id=None):
94 if filename is None and url is None and data is None:
94 if filename is None and url is None and data is None:
95 raise ValueError("No audio data found. Expecting filename, url, or data.")
95 raise ValueError("No audio data found. Expecting filename, url, or data.")
96 if embed is False and url is None:
96 if embed is False and url is None:
97 raise ValueError("No url found. Expecting url when embed=False")
97 raise ValueError("No url found. Expecting url when embed=False")
98
98
99 if url is not None and embed is not True:
99 if url is not None and embed is not True:
100 self.embed = False
100 self.embed = False
101 else:
101 else:
102 self.embed = True
102 self.embed = True
103 self.autoplay = autoplay
103 self.autoplay = autoplay
104 self.element_id = element_id
104 self.element_id = element_id
105 super(Audio, self).__init__(data=data, url=url, filename=filename)
105 super(Audio, self).__init__(data=data, url=url, filename=filename)
106
106
107 if self.data is not None and not isinstance(self.data, bytes):
107 if self.data is not None and not isinstance(self.data, bytes):
108 if rate is None:
108 if rate is None:
109 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.")
109 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.")
110 self.data = Audio._make_wav(data, rate, normalize)
110 self.data = Audio._make_wav(data, rate, normalize)
111
111
112 def reload(self):
112 def reload(self):
113 """Reload the raw data from file or URL."""
113 """Reload the raw data from file or URL."""
114 import mimetypes
114 import mimetypes
115 if self.embed:
115 if self.embed:
116 super(Audio, self).reload()
116 super(Audio, self).reload()
117
117
118 if self.filename is not None:
118 if self.filename is not None:
119 self.mimetype = mimetypes.guess_type(self.filename)[0]
119 self.mimetype = mimetypes.guess_type(self.filename)[0]
120 elif self.url is not None:
120 elif self.url is not None:
121 self.mimetype = mimetypes.guess_type(self.url)[0]
121 self.mimetype = mimetypes.guess_type(self.url)[0]
122 else:
122 else:
123 self.mimetype = "audio/wav"
123 self.mimetype = "audio/wav"
124
124
125 @staticmethod
125 @staticmethod
126 def _make_wav(data, rate, normalize):
126 def _make_wav(data, rate, normalize):
127 """ Transform a numpy array to a PCM bytestring """
127 """ Transform a numpy array to a PCM bytestring """
128 import struct
128 import struct
129 from io import BytesIO
129 from io import BytesIO
130 import wave
130 import wave
131
131
132 try:
132 try:
133 scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize)
133 scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize)
134 except ImportError:
134 except ImportError:
135 scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize)
135 scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize)
136
136
137 fp = BytesIO()
137 fp = BytesIO()
138 waveobj = wave.open(fp,mode='wb')
138 waveobj = wave.open(fp,mode='wb')
139 waveobj.setnchannels(nchan)
139 waveobj.setnchannels(nchan)
140 waveobj.setframerate(rate)
140 waveobj.setframerate(rate)
141 waveobj.setsampwidth(2)
141 waveobj.setsampwidth(2)
142 waveobj.setcomptype('NONE','NONE')
142 waveobj.setcomptype('NONE','NONE')
143 waveobj.writeframes(b''.join([struct.pack('<h',x) for x in scaled]))
143 waveobj.writeframes(b''.join([struct.pack('<h',x) for x in scaled]))
144 val = fp.getvalue()
144 val = fp.getvalue()
145 waveobj.close()
145 waveobj.close()
146
146
147 return val
147 return val
148
148
149 @staticmethod
149 @staticmethod
150 def _validate_and_normalize_with_numpy(data, normalize):
150 def _validate_and_normalize_with_numpy(data, normalize):
151 import numpy as np
151 import numpy as np
152
152
153 data = np.array(data, dtype=float)
153 data = np.array(data, dtype=float)
154 if len(data.shape) == 1:
154 if len(data.shape) == 1:
155 nchan = 1
155 nchan = 1
156 elif len(data.shape) == 2:
156 elif len(data.shape) == 2:
157 # In wave files,channels are interleaved. E.g.,
157 # In wave files,channels are interleaved. E.g.,
158 # "L1R1L2R2..." for stereo. See
158 # "L1R1L2R2..." for stereo. See
159 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
159 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
160 # for channel ordering
160 # for channel ordering
161 nchan = data.shape[0]
161 nchan = data.shape[0]
162 data = data.T.ravel()
162 data = data.T.ravel()
163 else:
163 else:
164 raise ValueError('Array audio input must be a 1D or 2D array')
164 raise ValueError('Array audio input must be a 1D or 2D array')
165
165
166 max_abs_value = np.max(np.abs(data))
166 max_abs_value = np.max(np.abs(data))
167 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
167 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
168 scaled = np.int16(data / normalization_factor * 32767).tolist()
168 scaled = np.int16(data / normalization_factor * 32767).tolist()
169 return scaled, nchan
169 return scaled, nchan
170
170
171
171
172 @staticmethod
172 @staticmethod
173 def _validate_and_normalize_without_numpy(data, normalize):
173 def _validate_and_normalize_without_numpy(data, normalize):
174 try:
174 try:
175 max_abs_value = float(max([abs(x) for x in data]))
175 max_abs_value = float(max([abs(x) for x in data]))
176 except TypeError:
176 except TypeError:
177 raise TypeError('Only lists of mono audio are '
177 raise TypeError('Only lists of mono audio are '
178 'supported if numpy is not installed')
178 'supported if numpy is not installed')
179
179
180 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
180 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
181 scaled = [int(x / normalization_factor * 32767) for x in data]
181 scaled = [int(x / normalization_factor * 32767) for x in data]
182 nchan = 1
182 nchan = 1
183 return scaled, nchan
183 return scaled, nchan
184
184
185 @staticmethod
185 @staticmethod
186 def _get_normalization_factor(max_abs_value, normalize):
186 def _get_normalization_factor(max_abs_value, normalize):
187 if not normalize and max_abs_value > 1:
187 if not normalize and max_abs_value > 1:
188 raise ValueError('Audio data must be between -1 and 1 when normalize=False.')
188 raise ValueError('Audio data must be between -1 and 1 when normalize=False.')
189 return max_abs_value if normalize else 1
189 return max_abs_value if normalize else 1
190
190
191 def _data_and_metadata(self):
191 def _data_and_metadata(self):
192 """shortcut for returning metadata with url information, if defined"""
192 """shortcut for returning metadata with url information, if defined"""
193 md = {}
193 md = {}
194 if self.url:
194 if self.url:
195 md['url'] = self.url
195 md['url'] = self.url
196 if md:
196 if md:
197 return self.data, md
197 return self.data, md
198 else:
198 else:
199 return self.data
199 return self.data
200
200
201 def _repr_html_(self):
201 def _repr_html_(self):
202 src = """
202 src = """
203 <audio {element_id} controls="controls" {autoplay}>
203 <audio {element_id} controls="controls" {autoplay}>
204 <source src="{src}" type="{type}" />
204 <source src="{src}" type="{type}" />
205 Your browser does not support the audio element.
205 Your browser does not support the audio element.
206 </audio>
206 </audio>
207 """
207 """
208 return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(),
208 return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(),
209 element_id=self.element_id_attr())
209 element_id=self.element_id_attr())
210
210
211 def src_attr(self):
211 def src_attr(self):
212 import base64
212 import base64
213 if self.embed and (self.data is not None):
213 if self.embed and (self.data is not None):
214 data = base64=base64.b64encode(self.data).decode('ascii')
214 data = base64=base64.b64encode(self.data).decode('ascii')
215 return """data:{type};base64,{base64}""".format(type=self.mimetype,
215 return """data:{type};base64,{base64}""".format(type=self.mimetype,
216 base64=data)
216 base64=data)
217 elif self.url is not None:
217 elif self.url is not None:
218 return self.url
218 return self.url
219 else:
219 else:
220 return ""
220 return ""
221
221
222 def autoplay_attr(self):
222 def autoplay_attr(self):
223 if(self.autoplay):
223 if(self.autoplay):
224 return 'autoplay="autoplay"'
224 return 'autoplay="autoplay"'
225 else:
225 else:
226 return ''
226 return ''
227
227
228 def element_id_attr(self):
228 def element_id_attr(self):
229 if (self.element_id):
229 if (self.element_id):
230 return 'id="{element_id}"'.format(element_id=self.element_id)
230 return 'id="{element_id}"'.format(element_id=self.element_id)
231 else:
231 else:
232 return ''
232 return ''
233
233
234 class IFrame(object):
234 class IFrame(object):
235 """
235 """
236 Generic class to embed an iframe in an IPython notebook
236 Generic class to embed an iframe in an IPython notebook
237 """
237 """
238
238
239 iframe = """
239 iframe = """
240 <iframe
240 <iframe
241 width="{width}"
241 width="{width}"
242 height="{height}"
242 height="{height}"
243 src="{src}{params}"
243 src="{src}{params}"
244 frameborder="0"
244 frameborder="0"
245 allowfullscreen
245 allowfullscreen
246 ></iframe>
246 ></iframe>
247 """
247 """
248
248
249 def __init__(self, src, width, height, **kwargs):
249 def __init__(self, src, width, height, **kwargs):
250 self.src = src
250 self.src = src
251 self.width = width
251 self.width = width
252 self.height = height
252 self.height = height
253 self.params = kwargs
253 self.params = kwargs
254
254
255 def _repr_html_(self):
255 def _repr_html_(self):
256 """return the embed iframe"""
256 """return the embed iframe"""
257 if self.params:
257 if self.params:
258 try:
258 try:
259 from urllib.parse import urlencode # Py 3
259 from urllib.parse import urlencode # Py 3
260 except ImportError:
260 except ImportError:
261 from urllib import urlencode
261 from urllib import urlencode
262 params = "?" + urlencode(self.params)
262 params = "?" + urlencode(self.params)
263 else:
263 else:
264 params = ""
264 params = ""
265 return self.iframe.format(src=self.src,
265 return self.iframe.format(src=self.src,
266 width=self.width,
266 width=self.width,
267 height=self.height,
267 height=self.height,
268 params=params)
268 params=params)
269
269
270 class YouTubeVideo(IFrame):
270 class YouTubeVideo(IFrame):
271 """Class for embedding a YouTube Video in an IPython session, based on its video id.
271 """Class for embedding a YouTube Video in an IPython session, based on its video id.
272
272
273 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
273 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
274 do::
274 do::
275
275
276 vid = YouTubeVideo("foo")
276 vid = YouTubeVideo("foo")
277 display(vid)
277 display(vid)
278
278
279 To start from 30 seconds::
279 To start from 30 seconds::
280
280
281 vid = YouTubeVideo("abc", start=30)
281 vid = YouTubeVideo("abc", start=30)
282 display(vid)
282 display(vid)
283
283
284 To calculate seconds from time as hours, minutes, seconds use
284 To calculate seconds from time as hours, minutes, seconds use
285 :class:`datetime.timedelta`::
285 :class:`datetime.timedelta`::
286
286
287 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
287 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
288
288
289 Other parameters can be provided as documented at
289 Other parameters can be provided as documented at
290 https://developers.google.com/youtube/player_parameters#Parameters
290 https://developers.google.com/youtube/player_parameters#Parameters
291
291
292 When converting the notebook using nbconvert, a jpeg representation of the video
292 When converting the notebook using nbconvert, a jpeg representation of the video
293 will be inserted in the document.
293 will be inserted in the document.
294 """
294 """
295
295
296 def __init__(self, id, width=400, height=300, **kwargs):
296 def __init__(self, id, width=400, height=300, **kwargs):
297 self.id=id
297 self.id=id
298 src = "https://www.youtube.com/embed/{0}".format(id)
298 src = "https://www.youtube.com/embed/{0}".format(id)
299 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
299 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
300
300
301 def _repr_jpeg_(self):
301 def _repr_jpeg_(self):
302 # Deferred import
302 # Deferred import
303 from urllib.request import urlopen
303 from urllib.request import urlopen
304
304
305 try:
305 try:
306 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
306 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
307 except IOError:
307 except IOError:
308 return None
308 return None
309
309
310 class VimeoVideo(IFrame):
310 class VimeoVideo(IFrame):
311 """
311 """
312 Class for embedding a Vimeo video in an IPython session, based on its video id.
312 Class for embedding a Vimeo video in an IPython session, based on its video id.
313 """
313 """
314
314
315 def __init__(self, id, width=400, height=300, **kwargs):
315 def __init__(self, id, width=400, height=300, **kwargs):
316 src="https://player.vimeo.com/video/{0}".format(id)
316 src="https://player.vimeo.com/video/{0}".format(id)
317 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
317 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
318
318
319 class ScribdDocument(IFrame):
319 class ScribdDocument(IFrame):
320 """
320 """
321 Class for embedding a Scribd document in an IPython session
321 Class for embedding a Scribd document in an IPython session
322
322
323 Use the start_page params to specify a starting point in the document
323 Use the start_page params to specify a starting point in the document
324 Use the view_mode params to specify display type one off scroll | slideshow | book
324 Use the view_mode params to specify display type one off scroll | slideshow | book
325
325
326 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
326 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
327
327
328 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
328 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
329 """
329 """
330
330
331 def __init__(self, id, width=400, height=300, **kwargs):
331 def __init__(self, id, width=400, height=300, **kwargs):
332 src="https://www.scribd.com/embeds/{0}/content".format(id)
332 src="https://www.scribd.com/embeds/{0}/content".format(id)
333 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
333 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
334
334
335 class FileLink(object):
335 class FileLink(object):
336 """Class for embedding a local file link in an IPython session, based on path
336 """Class for embedding a local file link in an IPython session, based on path
337
337
338 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
338 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
339
339
340 you would do::
340 you would do::
341
341
342 local_file = FileLink("my/data.txt")
342 local_file = FileLink("my/data.txt")
343 display(local_file)
343 display(local_file)
344
344
345 or in the HTML notebook, just::
345 or in the HTML notebook, just::
346
346
347 FileLink("my/data.txt")
347 FileLink("my/data.txt")
348 """
348 """
349
349
350 html_link_str = "<a href='%s' target='_blank'>%s</a>"
350 html_link_str = "<a href='%s' target='_blank'>%s</a>"
351
351
352 def __init__(self,
352 def __init__(self,
353 path,
353 path,
354 url_prefix='',
354 url_prefix='',
355 result_html_prefix='',
355 result_html_prefix='',
356 result_html_suffix='<br>'):
356 result_html_suffix='<br>'):
357 """
357 """
358 Parameters
358 Parameters
359 ----------
359 ----------
360 path : str
360 path : str
361 path to the file or directory that should be formatted
361 path to the file or directory that should be formatted
362 url_prefix : str
362 url_prefix : str
363 prefix to be prepended to all files to form a working link [default:
363 prefix to be prepended to all files to form a working link [default:
364 '']
364 '']
365 result_html_prefix : str
365 result_html_prefix : str
366 text to append to beginning to link [default: '']
366 text to append to beginning to link [default: '']
367 result_html_suffix : str
367 result_html_suffix : str
368 text to append at the end of link [default: '<br>']
368 text to append at the end of link [default: '<br>']
369 """
369 """
370 if isdir(path):
370 if isdir(path):
371 raise ValueError("Cannot display a directory using FileLink. "
371 raise ValueError("Cannot display a directory using FileLink. "
372 "Use FileLinks to display '%s'." % path)
372 "Use FileLinks to display '%s'." % path)
373 self.path = fsdecode(path)
373 self.path = fsdecode(path)
374 self.url_prefix = url_prefix
374 self.url_prefix = url_prefix
375 self.result_html_prefix = result_html_prefix
375 self.result_html_prefix = result_html_prefix
376 self.result_html_suffix = result_html_suffix
376 self.result_html_suffix = result_html_suffix
377
377
378 def _format_path(self):
378 def _format_path(self):
379 fp = ''.join([self.url_prefix, html_escape(self.path)])
379 fp = ''.join([self.url_prefix, html_escape(self.path)])
380 return ''.join([self.result_html_prefix,
380 return ''.join([self.result_html_prefix,
381 self.html_link_str % \
381 self.html_link_str % \
382 (fp, html_escape(self.path, quote=False)),
382 (fp, html_escape(self.path, quote=False)),
383 self.result_html_suffix])
383 self.result_html_suffix])
384
384
385 def _repr_html_(self):
385 def _repr_html_(self):
386 """return html link to file
386 """return html link to file
387 """
387 """
388 if not exists(self.path):
388 if not exists(self.path):
389 return ("Path (<tt>%s</tt>) doesn't exist. "
389 return ("Path (<tt>%s</tt>) doesn't exist. "
390 "It may still be in the process of "
390 "It may still be in the process of "
391 "being generated, or you may have the "
391 "being generated, or you may have the "
392 "incorrect path." % self.path)
392 "incorrect path." % self.path)
393
393
394 return self._format_path()
394 return self._format_path()
395
395
396 def __repr__(self):
396 def __repr__(self):
397 """return absolute path to file
397 """return absolute path to file
398 """
398 """
399 return abspath(self.path)
399 return abspath(self.path)
400
400
401 class FileLinks(FileLink):
401 class FileLinks(FileLink):
402 """Class for embedding local file links in an IPython session, based on path
402 """Class for embedding local file links in an IPython session, based on path
403
403
404 e.g. to embed links to files that were generated in the IPython notebook
404 e.g. to embed links to files that were generated in the IPython notebook
405 under ``my/data``, you would do::
405 under ``my/data``, you would do::
406
406
407 local_files = FileLinks("my/data")
407 local_files = FileLinks("my/data")
408 display(local_files)
408 display(local_files)
409
409
410 or in the HTML notebook, just::
410 or in the HTML notebook, just::
411
411
412 FileLinks("my/data")
412 FileLinks("my/data")
413 """
413 """
414 def __init__(self,
414 def __init__(self,
415 path,
415 path,
416 url_prefix='',
416 url_prefix='',
417 included_suffixes=None,
417 included_suffixes=None,
418 result_html_prefix='',
418 result_html_prefix='',
419 result_html_suffix='<br>',
419 result_html_suffix='<br>',
420 notebook_display_formatter=None,
420 notebook_display_formatter=None,
421 terminal_display_formatter=None,
421 terminal_display_formatter=None,
422 recursive=True):
422 recursive=True):
423 """
423 """
424 See :class:`FileLink` for the ``path``, ``url_prefix``,
424 See :class:`FileLink` for the ``path``, ``url_prefix``,
425 ``result_html_prefix`` and ``result_html_suffix`` parameters.
425 ``result_html_prefix`` and ``result_html_suffix`` parameters.
426
426
427 included_suffixes : list
427 included_suffixes : list
428 Filename suffixes to include when formatting output [default: include
428 Filename suffixes to include when formatting output [default: include
429 all files]
429 all files]
430
430
431 notebook_display_formatter : function
431 notebook_display_formatter : function
432 Used to format links for display in the notebook. See discussion of
432 Used to format links for display in the notebook. See discussion of
433 formatter functions below.
433 formatter functions below.
434
434
435 terminal_display_formatter : function
435 terminal_display_formatter : function
436 Used to format links for display in the terminal. See discussion of
436 Used to format links for display in the terminal. See discussion of
437 formatter functions below.
437 formatter functions below.
438
438
439 Formatter functions must be of the form::
439 Formatter functions must be of the form::
440
440
441 f(dirname, fnames, included_suffixes)
441 f(dirname, fnames, included_suffixes)
442
442
443 dirname : str
443 dirname : str
444 The name of a directory
444 The name of a directory
445 fnames : list
445 fnames : list
446 The files in that directory
446 The files in that directory
447 included_suffixes : list
447 included_suffixes : list
448 The file suffixes that should be included in the output (passing None
448 The file suffixes that should be included in the output (passing None
449 meansto include all suffixes in the output in the built-in formatters)
449 meansto include all suffixes in the output in the built-in formatters)
450 recursive : boolean
450 recursive : boolean
451 Whether to recurse into subdirectories. Default is True.
451 Whether to recurse into subdirectories. Default is True.
452
452
453 The function should return a list of lines that will be printed in the
453 The function should return a list of lines that will be printed in the
454 notebook (if passing notebook_display_formatter) or the terminal (if
454 notebook (if passing notebook_display_formatter) or the terminal (if
455 passing terminal_display_formatter). This function is iterated over for
455 passing terminal_display_formatter). This function is iterated over for
456 each directory in self.path. Default formatters are in place, can be
456 each directory in self.path. Default formatters are in place, can be
457 passed here to support alternative formatting.
457 passed here to support alternative formatting.
458
458
459 """
459 """
460 if isfile(path):
460 if isfile(path):
461 raise ValueError("Cannot display a file using FileLinks. "
461 raise ValueError("Cannot display a file using FileLinks. "
462 "Use FileLink to display '%s'." % path)
462 "Use FileLink to display '%s'." % path)
463 self.included_suffixes = included_suffixes
463 self.included_suffixes = included_suffixes
464 # remove trailing slashes for more consistent output formatting
464 # remove trailing slashes for more consistent output formatting
465 path = path.rstrip('/')
465 path = path.rstrip('/')
466
466
467 self.path = path
467 self.path = path
468 self.url_prefix = url_prefix
468 self.url_prefix = url_prefix
469 self.result_html_prefix = result_html_prefix
469 self.result_html_prefix = result_html_prefix
470 self.result_html_suffix = result_html_suffix
470 self.result_html_suffix = result_html_suffix
471
471
472 self.notebook_display_formatter = \
472 self.notebook_display_formatter = \
473 notebook_display_formatter or self._get_notebook_display_formatter()
473 notebook_display_formatter or self._get_notebook_display_formatter()
474 self.terminal_display_formatter = \
474 self.terminal_display_formatter = \
475 terminal_display_formatter or self._get_terminal_display_formatter()
475 terminal_display_formatter or self._get_terminal_display_formatter()
476
476
477 self.recursive = recursive
477 self.recursive = recursive
478
478
479 def _get_display_formatter(self,
479 def _get_display_formatter(self,
480 dirname_output_format,
480 dirname_output_format,
481 fname_output_format,
481 fname_output_format,
482 fp_format,
482 fp_format,
483 fp_cleaner=None):
483 fp_cleaner=None):
484 """ generate built-in formatter function
484 """ generate built-in formatter function
485
485
486 this is used to define both the notebook and terminal built-in
486 this is used to define both the notebook and terminal built-in
487 formatters as they only differ by some wrapper text for each entry
487 formatters as they only differ by some wrapper text for each entry
488
488
489 dirname_output_format: string to use for formatting directory
489 dirname_output_format: string to use for formatting directory
490 names, dirname will be substituted for a single "%s" which
490 names, dirname will be substituted for a single "%s" which
491 must appear in this string
491 must appear in this string
492 fname_output_format: string to use for formatting file names,
492 fname_output_format: string to use for formatting file names,
493 if a single "%s" appears in the string, fname will be substituted
493 if a single "%s" appears in the string, fname will be substituted
494 if two "%s" appear in the string, the path to fname will be
494 if two "%s" appear in the string, the path to fname will be
495 substituted for the first and fname will be substituted for the
495 substituted for the first and fname will be substituted for the
496 second
496 second
497 fp_format: string to use for formatting filepaths, must contain
497 fp_format: string to use for formatting filepaths, must contain
498 exactly two "%s" and the dirname will be subsituted for the first
498 exactly two "%s" and the dirname will be subsituted for the first
499 and fname will be substituted for the second
499 and fname will be substituted for the second
500 """
500 """
501 def f(dirname, fnames, included_suffixes=None):
501 def f(dirname, fnames, included_suffixes=None):
502 result = []
502 result = []
503 # begin by figuring out which filenames, if any,
503 # begin by figuring out which filenames, if any,
504 # are going to be displayed
504 # are going to be displayed
505 display_fnames = []
505 display_fnames = []
506 for fname in fnames:
506 for fname in fnames:
507 if (isfile(join(dirname,fname)) and
507 if (isfile(join(dirname,fname)) and
508 (included_suffixes is None or
508 (included_suffixes is None or
509 splitext(fname)[1] in included_suffixes)):
509 splitext(fname)[1] in included_suffixes)):
510 display_fnames.append(fname)
510 display_fnames.append(fname)
511
511
512 if len(display_fnames) == 0:
512 if len(display_fnames) == 0:
513 # if there are no filenames to display, don't print anything
513 # if there are no filenames to display, don't print anything
514 # (not even the directory name)
514 # (not even the directory name)
515 pass
515 pass
516 else:
516 else:
517 # otherwise print the formatted directory name followed by
517 # otherwise print the formatted directory name followed by
518 # the formatted filenames
518 # the formatted filenames
519 dirname_output_line = dirname_output_format % dirname
519 dirname_output_line = dirname_output_format % dirname
520 result.append(dirname_output_line)
520 result.append(dirname_output_line)
521 for fname in display_fnames:
521 for fname in display_fnames:
522 fp = fp_format % (dirname,fname)
522 fp = fp_format % (dirname,fname)
523 if fp_cleaner is not None:
523 if fp_cleaner is not None:
524 fp = fp_cleaner(fp)
524 fp = fp_cleaner(fp)
525 try:
525 try:
526 # output can include both a filepath and a filename...
526 # output can include both a filepath and a filename...
527 fname_output_line = fname_output_format % (fp, fname)
527 fname_output_line = fname_output_format % (fp, fname)
528 except TypeError:
528 except TypeError:
529 # ... or just a single filepath
529 # ... or just a single filepath
530 fname_output_line = fname_output_format % fname
530 fname_output_line = fname_output_format % fname
531 result.append(fname_output_line)
531 result.append(fname_output_line)
532 return result
532 return result
533 return f
533 return f
534
534
535 def _get_notebook_display_formatter(self,
535 def _get_notebook_display_formatter(self,
536 spacer="&nbsp;&nbsp;"):
536 spacer="&nbsp;&nbsp;"):
537 """ generate function to use for notebook formatting
537 """ generate function to use for notebook formatting
538 """
538 """
539 dirname_output_format = \
539 dirname_output_format = \
540 self.result_html_prefix + "%s/" + self.result_html_suffix
540 self.result_html_prefix + "%s/" + self.result_html_suffix
541 fname_output_format = \
541 fname_output_format = \
542 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
542 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
543 fp_format = self.url_prefix + '%s/%s'
543 fp_format = self.url_prefix + '%s/%s'
544 if sep == "\\":
544 if sep == "\\":
545 # Working on a platform where the path separator is "\", so
545 # Working on a platform where the path separator is "\", so
546 # must convert these to "/" for generating a URI
546 # must convert these to "/" for generating a URI
547 def fp_cleaner(fp):
547 def fp_cleaner(fp):
548 # Replace all occurrences of backslash ("\") with a forward
548 # Replace all occurrences of backslash ("\") with a forward
549 # slash ("/") - this is necessary on windows when a path is
549 # slash ("/") - this is necessary on windows when a path is
550 # provided as input, but we must link to a URI
550 # provided as input, but we must link to a URI
551 return fp.replace('\\','/')
551 return fp.replace('\\','/')
552 else:
552 else:
553 fp_cleaner = None
553 fp_cleaner = None
554
554
555 return self._get_display_formatter(dirname_output_format,
555 return self._get_display_formatter(dirname_output_format,
556 fname_output_format,
556 fname_output_format,
557 fp_format,
557 fp_format,
558 fp_cleaner)
558 fp_cleaner)
559
559
560 def _get_terminal_display_formatter(self,
560 def _get_terminal_display_formatter(self,
561 spacer=" "):
561 spacer=" "):
562 """ generate function to use for terminal formatting
562 """ generate function to use for terminal formatting
563 """
563 """
564 dirname_output_format = "%s/"
564 dirname_output_format = "%s/"
565 fname_output_format = spacer + "%s"
565 fname_output_format = spacer + "%s"
566 fp_format = '%s/%s'
566 fp_format = '%s/%s'
567
567
568 return self._get_display_formatter(dirname_output_format,
568 return self._get_display_formatter(dirname_output_format,
569 fname_output_format,
569 fname_output_format,
570 fp_format)
570 fp_format)
571
571
572 def _format_path(self):
572 def _format_path(self):
573 result_lines = []
573 result_lines = []
574 if self.recursive:
574 if self.recursive:
575 walked_dir = list(walk(self.path))
575 walked_dir = list(walk(self.path))
576 else:
576 else:
577 walked_dir = [next(walk(self.path))]
577 walked_dir = [next(walk(self.path))]
578 walked_dir.sort()
578 walked_dir.sort()
579 for dirname, subdirs, fnames in walked_dir:
579 for dirname, subdirs, fnames in walked_dir:
580 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
580 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
581 return '\n'.join(result_lines)
581 return '\n'.join(result_lines)
582
582
583 def __repr__(self):
583 def __repr__(self):
584 """return newline-separated absolute paths
584 """return newline-separated absolute paths
585 """
585 """
586 result_lines = []
586 result_lines = []
587 if self.recursive:
587 if self.recursive:
588 walked_dir = list(walk(self.path))
588 walked_dir = list(walk(self.path))
589 else:
589 else:
590 walked_dir = [next(walk(self.path))]
590 walked_dir = [next(walk(self.path))]
591 walked_dir.sort()
591 walked_dir.sort()
592 for dirname, subdirs, fnames in walked_dir:
592 for dirname, subdirs, fnames in walked_dir:
593 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
593 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
594 return '\n'.join(result_lines)
594 return '\n'.join(result_lines)
595
595
596
596
597 class Code(TextDisplayObject):
597 class Code(TextDisplayObject):
598 """Display syntax-highlighted source code.
598 """Display syntax-highlighted source code.
599
599
600 This uses Pygments to highlight the code for HTML and Latex output.
600 This uses Pygments to highlight the code for HTML and Latex output.
601
601
602 Parameters
602 Parameters
603 ----------
603 ----------
604 data : str
604 data : str
605 The code as a string
605 The code as a string
606 url : str
606 url : str
607 A URL to fetch the code from
607 A URL to fetch the code from
608 filename : str
608 filename : str
609 A local filename to load the code from
609 A local filename to load the code from
610 language : str
610 language : str
611 The short name of a Pygments lexer to use for highlighting.
611 The short name of a Pygments lexer to use for highlighting.
612 If not specified, it will guess the lexer based on the filename
612 If not specified, it will guess the lexer based on the filename
613 or the code. Available lexers: http://pygments.org/docs/lexers/
613 or the code. Available lexers: http://pygments.org/docs/lexers/
614 """
614 """
615 def __init__(self, data=None, url=None, filename=None, language=None):
615 def __init__(self, data=None, url=None, filename=None, language=None):
616 self.language = language
616 self.language = language
617 super().__init__(data=data, url=url, filename=filename)
617 super().__init__(data=data, url=url, filename=filename)
618
618
619 def _get_lexer(self):
619 def _get_lexer(self):
620 if self.language:
620 if self.language:
621 from pygments.lexers import get_lexer_by_name
621 from pygments.lexers import get_lexer_by_name
622 return get_lexer_by_name(self.language)
622 return get_lexer_by_name(self.language)
623 elif self.filename:
623 elif self.filename:
624 from pygments.lexers import get_lexer_for_filename
624 from pygments.lexers import get_lexer_for_filename
625 return get_lexer_for_filename(self.filename)
625 return get_lexer_for_filename(self.filename)
626 else:
626 else:
627 from pygments.lexers import guess_lexer
627 from pygments.lexers import guess_lexer
628 return guess_lexer(self.data)
628 return guess_lexer(self.data)
629
629
630 def __repr__(self):
630 def __repr__(self):
631 return self.data
631 return self.data
632
632
633 def _repr_html_(self):
633 def _repr_html_(self):
634 from pygments import highlight
634 from pygments import highlight
635 from pygments.formatters import HtmlFormatter
635 from pygments.formatters import HtmlFormatter
636 fmt = HtmlFormatter()
636 fmt = HtmlFormatter()
637 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
637 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
638 return style + highlight(self.data, self._get_lexer(), fmt)
638 return style + highlight(self.data, self._get_lexer(), fmt)
639
639
640 def _repr_latex_(self):
640 def _repr_latex_(self):
641 from pygments import highlight
641 from pygments import highlight
642 from pygments.formatters import LatexFormatter
642 from pygments.formatters import LatexFormatter
643 return highlight(self.data, self._get_lexer(), LatexFormatter())
643 return highlight(self.data, self._get_lexer(), LatexFormatter())
General Comments 0
You need to be logged in to leave comments. Login now