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