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