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