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