##// END OF EJS Templates
move io.unicode_std_stream to nbconvert.utils.io
Min RK -
Show More
@@ -0,0 +1,33 b''
1 # coding: utf-8
2 """io-related utilities"""
3
4 # Copyright (c) Jupyter Development Team.
5 # Distributed under the terms of the Modified BSD License.
6
7 import codecs
8 import sys
9 from IPython.utils.py3compat import PY3
10
11
12 def unicode_std_stream(stream='stdout'):
13 u"""Get a wrapper to write unicode to stdout/stderr as UTF-8.
14
15 This ignores environment variables and default encodings, to reliably write
16 unicode to stdout or stderr.
17
18 ::
19
20 unicode_std_stream().write(u'Ε‚@e¢ŧ←')
21 """
22 assert stream in ('stdout', 'stderr')
23 stream = getattr(sys, stream)
24 if PY3:
25 try:
26 stream_b = stream.buffer
27 except AttributeError:
28 # sys.stdout has been replaced - use it directly
29 return stream
30 else:
31 stream_b = stream
32
33 return codecs.getwriter('utf-8')(stream_b)
@@ -0,0 +1,50 b''
1 # encoding: utf-8
2 """Tests for utils.io"""
3
4 # Copyright (c) Jupyter Development Team.
5 # Distributed under the terms of the Modified BSD License.
6
7 import io as stdlib_io
8 import sys
9
10 import nose.tools as nt
11
12 from IPython.testing.decorators import skipif
13 from ..io import unicode_std_stream
14 from IPython.utils.py3compat import PY3
15
16 if PY3:
17 from io import StringIO
18 else:
19 from StringIO import StringIO
20
21 def test_UnicodeStdStream():
22 # Test wrapping a bytes-level stdout
23 if PY3:
24 stdoutb = stdlib_io.BytesIO()
25 stdout = stdlib_io.TextIOWrapper(stdoutb, encoding='ascii')
26 else:
27 stdout = stdoutb = stdlib_io.BytesIO()
28
29 orig_stdout = sys.stdout
30 sys.stdout = stdout
31 try:
32 sample = u"@Ε‚e¢ŧ←"
33 unicode_std_stream().write(sample)
34
35 output = stdoutb.getvalue().decode('utf-8')
36 nt.assert_equal(output, sample)
37 assert not stdout.closed
38 finally:
39 sys.stdout = orig_stdout
40
41 @skipif(not PY3, "Not applicable on Python 2")
42 def test_UnicodeStdStream_nowrap():
43 # If we replace stdout with a StringIO, it shouldn't get wrapped.
44 orig_stdout = sys.stdout
45 sys.stdout = StringIO()
46 try:
47 nt.assert_is(unicode_std_stream(), sys.stdout)
48 assert not sys.stdout.closed
49 finally:
50 sys.stdout = orig_stdout
@@ -1,347 +1,322 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 IO related utilities.
3 IO related utilities.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 # Copyright (c) IPython Development Team.
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Distributed under the terms of the Modified BSD License.
8 #
8
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
12 from __future__ import print_function
9 from __future__ import print_function
13 from __future__ import absolute_import
10 from __future__ import absolute_import
14
11
15 #-----------------------------------------------------------------------------
12
16 # Imports
17 #-----------------------------------------------------------------------------
18 import codecs
13 import codecs
19 from contextlib import contextmanager
14 from contextlib import contextmanager
20 import io
15 import io
21 import os
16 import os
22 import shutil
17 import shutil
23 import stat
24 import sys
18 import sys
25 import tempfile
19 import tempfile
20 import warnings
26 from .capture import CapturedIO, capture_output
21 from .capture import CapturedIO, capture_output
27 from .py3compat import string_types, input, PY3
22 from .py3compat import string_types, input, PY3
28
23
29 #-----------------------------------------------------------------------------
30 # Code
31 #-----------------------------------------------------------------------------
32
33
24
34 class IOStream:
25 class IOStream:
35
26
36 def __init__(self,stream, fallback=None):
27 def __init__(self,stream, fallback=None):
37 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
28 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
38 if fallback is not None:
29 if fallback is not None:
39 stream = fallback
30 stream = fallback
40 else:
31 else:
41 raise ValueError("fallback required, but not specified")
32 raise ValueError("fallback required, but not specified")
42 self.stream = stream
33 self.stream = stream
43 self._swrite = stream.write
34 self._swrite = stream.write
44
35
45 # clone all methods not overridden:
36 # clone all methods not overridden:
46 def clone(meth):
37 def clone(meth):
47 return not hasattr(self, meth) and not meth.startswith('_')
38 return not hasattr(self, meth) and not meth.startswith('_')
48 for meth in filter(clone, dir(stream)):
39 for meth in filter(clone, dir(stream)):
49 setattr(self, meth, getattr(stream, meth))
40 setattr(self, meth, getattr(stream, meth))
50
41
51 def __repr__(self):
42 def __repr__(self):
52 cls = self.__class__
43 cls = self.__class__
53 tpl = '{mod}.{cls}({args})'
44 tpl = '{mod}.{cls}({args})'
54 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
45 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
55
46
56 def write(self,data):
47 def write(self,data):
57 try:
48 try:
58 self._swrite(data)
49 self._swrite(data)
59 except:
50 except:
60 try:
51 try:
61 # print handles some unicode issues which may trip a plain
52 # print handles some unicode issues which may trip a plain
62 # write() call. Emulate write() by using an empty end
53 # write() call. Emulate write() by using an empty end
63 # argument.
54 # argument.
64 print(data, end='', file=self.stream)
55 print(data, end='', file=self.stream)
65 except:
56 except:
66 # if we get here, something is seriously broken.
57 # if we get here, something is seriously broken.
67 print('ERROR - failed to write data to stream:', self.stream,
58 print('ERROR - failed to write data to stream:', self.stream,
68 file=sys.stderr)
59 file=sys.stderr)
69
60
70 def writelines(self, lines):
61 def writelines(self, lines):
71 if isinstance(lines, string_types):
62 if isinstance(lines, string_types):
72 lines = [lines]
63 lines = [lines]
73 for line in lines:
64 for line in lines:
74 self.write(line)
65 self.write(line)
75
66
76 # This class used to have a writeln method, but regular files and streams
67 # This class used to have a writeln method, but regular files and streams
77 # in Python don't have this method. We need to keep this completely
68 # in Python don't have this method. We need to keep this completely
78 # compatible so we removed it.
69 # compatible so we removed it.
79
70
80 @property
71 @property
81 def closed(self):
72 def closed(self):
82 return self.stream.closed
73 return self.stream.closed
83
74
84 def close(self):
75 def close(self):
85 pass
76 pass
86
77
87 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
78 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
88 devnull = open(os.devnull, 'w')
79 devnull = open(os.devnull, 'w')
89 stdin = IOStream(sys.stdin, fallback=devnull)
80 stdin = IOStream(sys.stdin, fallback=devnull)
90 stdout = IOStream(sys.stdout, fallback=devnull)
81 stdout = IOStream(sys.stdout, fallback=devnull)
91 stderr = IOStream(sys.stderr, fallback=devnull)
82 stderr = IOStream(sys.stderr, fallback=devnull)
92
83
93 class IOTerm:
84 class IOTerm:
94 """ Term holds the file or file-like objects for handling I/O operations.
85 """ Term holds the file or file-like objects for handling I/O operations.
95
86
96 These are normally just sys.stdin, sys.stdout and sys.stderr but for
87 These are normally just sys.stdin, sys.stdout and sys.stderr but for
97 Windows they can can replaced to allow editing the strings before they are
88 Windows they can can replaced to allow editing the strings before they are
98 displayed."""
89 displayed."""
99
90
100 # In the future, having IPython channel all its I/O operations through
91 # In the future, having IPython channel all its I/O operations through
101 # this class will make it easier to embed it into other environments which
92 # this class will make it easier to embed it into other environments which
102 # are not a normal terminal (such as a GUI-based shell)
93 # are not a normal terminal (such as a GUI-based shell)
103 def __init__(self, stdin=None, stdout=None, stderr=None):
94 def __init__(self, stdin=None, stdout=None, stderr=None):
104 mymodule = sys.modules[__name__]
95 mymodule = sys.modules[__name__]
105 self.stdin = IOStream(stdin, mymodule.stdin)
96 self.stdin = IOStream(stdin, mymodule.stdin)
106 self.stdout = IOStream(stdout, mymodule.stdout)
97 self.stdout = IOStream(stdout, mymodule.stdout)
107 self.stderr = IOStream(stderr, mymodule.stderr)
98 self.stderr = IOStream(stderr, mymodule.stderr)
108
99
109
100
110 class Tee(object):
101 class Tee(object):
111 """A class to duplicate an output stream to stdout/err.
102 """A class to duplicate an output stream to stdout/err.
112
103
113 This works in a manner very similar to the Unix 'tee' command.
104 This works in a manner very similar to the Unix 'tee' command.
114
105
115 When the object is closed or deleted, it closes the original file given to
106 When the object is closed or deleted, it closes the original file given to
116 it for duplication.
107 it for duplication.
117 """
108 """
118 # Inspired by:
109 # Inspired by:
119 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
110 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
120
111
121 def __init__(self, file_or_name, mode="w", channel='stdout'):
112 def __init__(self, file_or_name, mode="w", channel='stdout'):
122 """Construct a new Tee object.
113 """Construct a new Tee object.
123
114
124 Parameters
115 Parameters
125 ----------
116 ----------
126 file_or_name : filename or open filehandle (writable)
117 file_or_name : filename or open filehandle (writable)
127 File that will be duplicated
118 File that will be duplicated
128
119
129 mode : optional, valid mode for open().
120 mode : optional, valid mode for open().
130 If a filename was give, open with this mode.
121 If a filename was give, open with this mode.
131
122
132 channel : str, one of ['stdout', 'stderr']
123 channel : str, one of ['stdout', 'stderr']
133 """
124 """
134 if channel not in ['stdout', 'stderr']:
125 if channel not in ['stdout', 'stderr']:
135 raise ValueError('Invalid channel spec %s' % channel)
126 raise ValueError('Invalid channel spec %s' % channel)
136
127
137 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
128 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
138 self.file = file_or_name
129 self.file = file_or_name
139 else:
130 else:
140 self.file = open(file_or_name, mode)
131 self.file = open(file_or_name, mode)
141 self.channel = channel
132 self.channel = channel
142 self.ostream = getattr(sys, channel)
133 self.ostream = getattr(sys, channel)
143 setattr(sys, channel, self)
134 setattr(sys, channel, self)
144 self._closed = False
135 self._closed = False
145
136
146 def close(self):
137 def close(self):
147 """Close the file and restore the channel."""
138 """Close the file and restore the channel."""
148 self.flush()
139 self.flush()
149 setattr(sys, self.channel, self.ostream)
140 setattr(sys, self.channel, self.ostream)
150 self.file.close()
141 self.file.close()
151 self._closed = True
142 self._closed = True
152
143
153 def write(self, data):
144 def write(self, data):
154 """Write data to both channels."""
145 """Write data to both channels."""
155 self.file.write(data)
146 self.file.write(data)
156 self.ostream.write(data)
147 self.ostream.write(data)
157 self.ostream.flush()
148 self.ostream.flush()
158
149
159 def flush(self):
150 def flush(self):
160 """Flush both channels."""
151 """Flush both channels."""
161 self.file.flush()
152 self.file.flush()
162 self.ostream.flush()
153 self.ostream.flush()
163
154
164 def __del__(self):
155 def __del__(self):
165 if not self._closed:
156 if not self._closed:
166 self.close()
157 self.close()
167
158
168
159
169 def ask_yes_no(prompt, default=None, interrupt=None):
160 def ask_yes_no(prompt, default=None, interrupt=None):
170 """Asks a question and returns a boolean (y/n) answer.
161 """Asks a question and returns a boolean (y/n) answer.
171
162
172 If default is given (one of 'y','n'), it is used if the user input is
163 If default is given (one of 'y','n'), it is used if the user input is
173 empty. If interrupt is given (one of 'y','n'), it is used if the user
164 empty. If interrupt is given (one of 'y','n'), it is used if the user
174 presses Ctrl-C. Otherwise the question is repeated until an answer is
165 presses Ctrl-C. Otherwise the question is repeated until an answer is
175 given.
166 given.
176
167
177 An EOF is treated as the default answer. If there is no default, an
168 An EOF is treated as the default answer. If there is no default, an
178 exception is raised to prevent infinite loops.
169 exception is raised to prevent infinite loops.
179
170
180 Valid answers are: y/yes/n/no (match is not case sensitive)."""
171 Valid answers are: y/yes/n/no (match is not case sensitive)."""
181
172
182 answers = {'y':True,'n':False,'yes':True,'no':False}
173 answers = {'y':True,'n':False,'yes':True,'no':False}
183 ans = None
174 ans = None
184 while ans not in answers.keys():
175 while ans not in answers.keys():
185 try:
176 try:
186 ans = input(prompt+' ').lower()
177 ans = input(prompt+' ').lower()
187 if not ans: # response was an empty string
178 if not ans: # response was an empty string
188 ans = default
179 ans = default
189 except KeyboardInterrupt:
180 except KeyboardInterrupt:
190 if interrupt:
181 if interrupt:
191 ans = interrupt
182 ans = interrupt
192 except EOFError:
183 except EOFError:
193 if default in answers.keys():
184 if default in answers.keys():
194 ans = default
185 ans = default
195 print()
186 print()
196 else:
187 else:
197 raise
188 raise
198
189
199 return answers[ans]
190 return answers[ans]
200
191
201
192
202 def temp_pyfile(src, ext='.py'):
193 def temp_pyfile(src, ext='.py'):
203 """Make a temporary python file, return filename and filehandle.
194 """Make a temporary python file, return filename and filehandle.
204
195
205 Parameters
196 Parameters
206 ----------
197 ----------
207 src : string or list of strings (no need for ending newlines if list)
198 src : string or list of strings (no need for ending newlines if list)
208 Source code to be written to the file.
199 Source code to be written to the file.
209
200
210 ext : optional, string
201 ext : optional, string
211 Extension for the generated file.
202 Extension for the generated file.
212
203
213 Returns
204 Returns
214 -------
205 -------
215 (filename, open filehandle)
206 (filename, open filehandle)
216 It is the caller's responsibility to close the open file and unlink it.
207 It is the caller's responsibility to close the open file and unlink it.
217 """
208 """
218 fname = tempfile.mkstemp(ext)[1]
209 fname = tempfile.mkstemp(ext)[1]
219 f = open(fname,'w')
210 f = open(fname,'w')
220 f.write(src)
211 f.write(src)
221 f.flush()
212 f.flush()
222 return fname, f
213 return fname, f
223
214
224 def _copy_metadata(src, dst):
215 def _copy_metadata(src, dst):
225 """Copy the set of metadata we want for atomic_writing.
216 """Copy the set of metadata we want for atomic_writing.
226
217
227 Permission bits and flags. We'd like to copy file ownership as well, but we
218 Permission bits and flags. We'd like to copy file ownership as well, but we
228 can't do that.
219 can't do that.
229 """
220 """
230 shutil.copymode(src, dst)
221 shutil.copymode(src, dst)
231 st = os.stat(src)
222 st = os.stat(src)
232 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
223 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
233 os.chflags(dst, st.st_flags)
224 os.chflags(dst, st.st_flags)
234
225
235 @contextmanager
226 @contextmanager
236 def atomic_writing(path, text=True, encoding='utf-8', **kwargs):
227 def atomic_writing(path, text=True, encoding='utf-8', **kwargs):
237 """Context manager to write to a file only if the entire write is successful.
228 """Context manager to write to a file only if the entire write is successful.
238
229
239 This works by creating a temporary file in the same directory, and renaming
230 This works by creating a temporary file in the same directory, and renaming
240 it over the old file if the context is exited without an error. If other
231 it over the old file if the context is exited without an error. If other
241 file names are hard linked to the target file, this relationship will not be
232 file names are hard linked to the target file, this relationship will not be
242 preserved.
233 preserved.
243
234
244 On Windows, there is a small chink in the atomicity: the target file is
235 On Windows, there is a small chink in the atomicity: the target file is
245 deleted before renaming the temporary file over it. This appears to be
236 deleted before renaming the temporary file over it. This appears to be
246 unavoidable.
237 unavoidable.
247
238
248 Parameters
239 Parameters
249 ----------
240 ----------
250 path : str
241 path : str
251 The target file to write to.
242 The target file to write to.
252
243
253 text : bool, optional
244 text : bool, optional
254 Whether to open the file in text mode (i.e. to write unicode). Default is
245 Whether to open the file in text mode (i.e. to write unicode). Default is
255 True.
246 True.
256
247
257 encoding : str, optional
248 encoding : str, optional
258 The encoding to use for files opened in text mode. Default is UTF-8.
249 The encoding to use for files opened in text mode. Default is UTF-8.
259
250
260 **kwargs
251 **kwargs
261 Passed to :func:`io.open`.
252 Passed to :func:`io.open`.
262 """
253 """
263 # realpath doesn't work on Windows: http://bugs.python.org/issue9949
254 # realpath doesn't work on Windows: http://bugs.python.org/issue9949
264 # Luckily, we only need to resolve the file itself being a symlink, not
255 # Luckily, we only need to resolve the file itself being a symlink, not
265 # any of its directories, so this will suffice:
256 # any of its directories, so this will suffice:
266 if os.path.islink(path):
257 if os.path.islink(path):
267 path = os.path.join(os.path.dirname(path), os.readlink(path))
258 path = os.path.join(os.path.dirname(path), os.readlink(path))
268
259
269 dirname, basename = os.path.split(path)
260 dirname, basename = os.path.split(path)
270 tmp_dir = tempfile.mkdtemp(prefix=basename, dir=dirname)
261 tmp_dir = tempfile.mkdtemp(prefix=basename, dir=dirname)
271 tmp_path = os.path.join(tmp_dir, basename)
262 tmp_path = os.path.join(tmp_dir, basename)
272 if text:
263 if text:
273 fileobj = io.open(tmp_path, 'w', encoding=encoding, **kwargs)
264 fileobj = io.open(tmp_path, 'w', encoding=encoding, **kwargs)
274 else:
265 else:
275 fileobj = io.open(tmp_path, 'wb', **kwargs)
266 fileobj = io.open(tmp_path, 'wb', **kwargs)
276
267
277 try:
268 try:
278 yield fileobj
269 yield fileobj
279 except:
270 except:
280 fileobj.close()
271 fileobj.close()
281 shutil.rmtree(tmp_dir)
272 shutil.rmtree(tmp_dir)
282 raise
273 raise
283
274
284 # Flush to disk
275 # Flush to disk
285 fileobj.flush()
276 fileobj.flush()
286 os.fsync(fileobj.fileno())
277 os.fsync(fileobj.fileno())
287
278
288 # Written successfully, now rename it
279 # Written successfully, now rename it
289 fileobj.close()
280 fileobj.close()
290
281
291 # Copy permission bits, access time, etc.
282 # Copy permission bits, access time, etc.
292 try:
283 try:
293 _copy_metadata(path, tmp_path)
284 _copy_metadata(path, tmp_path)
294 except OSError:
285 except OSError:
295 # e.g. the file didn't already exist. Ignore any failure to copy metadata
286 # e.g. the file didn't already exist. Ignore any failure to copy metadata
296 pass
287 pass
297
288
298 if os.name == 'nt' and os.path.exists(path):
289 if os.name == 'nt' and os.path.exists(path):
299 # Rename over existing file doesn't work on Windows
290 # Rename over existing file doesn't work on Windows
300 os.remove(path)
291 os.remove(path)
301
292
302 os.rename(tmp_path, path)
293 os.rename(tmp_path, path)
303 shutil.rmtree(tmp_dir)
294 shutil.rmtree(tmp_dir)
304
295
305
296
306 def raw_print(*args, **kw):
297 def raw_print(*args, **kw):
307 """Raw print to sys.__stdout__, otherwise identical interface to print()."""
298 """Raw print to sys.__stdout__, otherwise identical interface to print()."""
308
299
309 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
300 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
310 file=sys.__stdout__)
301 file=sys.__stdout__)
311 sys.__stdout__.flush()
302 sys.__stdout__.flush()
312
303
313
304
314 def raw_print_err(*args, **kw):
305 def raw_print_err(*args, **kw):
315 """Raw print to sys.__stderr__, otherwise identical interface to print()."""
306 """Raw print to sys.__stderr__, otherwise identical interface to print()."""
316
307
317 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
308 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
318 file=sys.__stderr__)
309 file=sys.__stderr__)
319 sys.__stderr__.flush()
310 sys.__stderr__.flush()
320
311
321
312
322 # Short aliases for quick debugging, do NOT use these in production code.
313 # Short aliases for quick debugging, do NOT use these in production code.
323 rprint = raw_print
314 rprint = raw_print
324 rprinte = raw_print_err
315 rprinte = raw_print_err
325
316
326 def unicode_std_stream(stream='stdout'):
327 u"""Get a wrapper to write unicode to stdout/stderr as UTF-8.
328
329 This ignores environment variables and default encodings, to reliably write
330 unicode to stdout or stderr.
331
332 ::
333
317
334 unicode_std_stream().write(u'Ε‚@e¢ŧ←')
318 def unicode_std_stream(stream='stdout'):
335 """
319 """DEPRECATED, moved to jupyter_nbconvert.utils.io"""
336 assert stream in ('stdout', 'stderr')
320 warn("IPython.utils.io.unicode_std_stream has moved to jupyter_nbconvert.utils.io")
337 stream = getattr(sys, stream)
321 from jupyter_nbconvert.utils.io import unicode_std_stream
338 if PY3:
322 return unicode_std_stream(stream)
339 try:
340 stream_b = stream.buffer
341 except AttributeError:
342 # sys.stdout has been replaced - use it directly
343 return stream
344 else:
345 stream_b = stream
346
347 return codecs.getwriter('utf-8')(stream_b)
@@ -1,231 +1,201 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for io.py"""
2 """Tests for io.py"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import io as stdlib_io
10 import io as stdlib_io
11 import os.path
11 import os.path
12 import stat
12 import stat
13 import sys
13 import sys
14
14
15 from subprocess import Popen, PIPE
15 from subprocess import Popen, PIPE
16 import unittest
16 import unittest
17
17
18 import nose.tools as nt
18 import nose.tools as nt
19
19
20 from IPython.testing.decorators import skipif, skip_win32
20 from IPython.testing.decorators import skipif, skip_win32
21 from IPython.utils.io import (Tee, capture_output, unicode_std_stream,
21 from IPython.utils.io import (Tee, capture_output, unicode_std_stream,
22 atomic_writing,
22 atomic_writing,
23 )
23 )
24 from IPython.utils.py3compat import doctest_refactor_print, PY3
24 from IPython.utils.py3compat import doctest_refactor_print, PY3
25 from IPython.utils.tempdir import TemporaryDirectory
25 from IPython.utils.tempdir import TemporaryDirectory
26
26
27 if PY3:
27 if PY3:
28 from io import StringIO
28 from io import StringIO
29 else:
29 else:
30 from StringIO import StringIO
30 from StringIO import StringIO
31
31
32
32
33 def test_tee_simple():
33 def test_tee_simple():
34 "Very simple check with stdout only"
34 "Very simple check with stdout only"
35 chan = StringIO()
35 chan = StringIO()
36 text = 'Hello'
36 text = 'Hello'
37 tee = Tee(chan, channel='stdout')
37 tee = Tee(chan, channel='stdout')
38 print(text, file=chan)
38 print(text, file=chan)
39 nt.assert_equal(chan.getvalue(), text+"\n")
39 nt.assert_equal(chan.getvalue(), text+"\n")
40
40
41
41
42 class TeeTestCase(unittest.TestCase):
42 class TeeTestCase(unittest.TestCase):
43
43
44 def tchan(self, channel, check='close'):
44 def tchan(self, channel, check='close'):
45 trap = StringIO()
45 trap = StringIO()
46 chan = StringIO()
46 chan = StringIO()
47 text = 'Hello'
47 text = 'Hello'
48
48
49 std_ori = getattr(sys, channel)
49 std_ori = getattr(sys, channel)
50 setattr(sys, channel, trap)
50 setattr(sys, channel, trap)
51
51
52 tee = Tee(chan, channel=channel)
52 tee = Tee(chan, channel=channel)
53 print(text, end='', file=chan)
53 print(text, end='', file=chan)
54 setattr(sys, channel, std_ori)
54 setattr(sys, channel, std_ori)
55 trap_val = trap.getvalue()
55 trap_val = trap.getvalue()
56 nt.assert_equal(chan.getvalue(), text)
56 nt.assert_equal(chan.getvalue(), text)
57 if check=='close':
57 if check=='close':
58 tee.close()
58 tee.close()
59 else:
59 else:
60 del tee
60 del tee
61
61
62 def test(self):
62 def test(self):
63 for chan in ['stdout', 'stderr']:
63 for chan in ['stdout', 'stderr']:
64 for check in ['close', 'del']:
64 for check in ['close', 'del']:
65 self.tchan(chan, check)
65 self.tchan(chan, check)
66
66
67 def test_io_init():
67 def test_io_init():
68 """Test that io.stdin/out/err exist at startup"""
68 """Test that io.stdin/out/err exist at startup"""
69 for name in ('stdin', 'stdout', 'stderr'):
69 for name in ('stdin', 'stdout', 'stderr'):
70 cmd = doctest_refactor_print("from IPython.utils import io;print io.%s.__class__"%name)
70 cmd = doctest_refactor_print("from IPython.utils import io;print io.%s.__class__"%name)
71 p = Popen([sys.executable, '-c', cmd],
71 p = Popen([sys.executable, '-c', cmd],
72 stdout=PIPE)
72 stdout=PIPE)
73 p.wait()
73 p.wait()
74 classname = p.stdout.read().strip().decode('ascii')
74 classname = p.stdout.read().strip().decode('ascii')
75 # __class__ is a reference to the class object in Python 3, so we can't
75 # __class__ is a reference to the class object in Python 3, so we can't
76 # just test for string equality.
76 # just test for string equality.
77 assert 'IPython.utils.io.IOStream' in classname, classname
77 assert 'IPython.utils.io.IOStream' in classname, classname
78
78
79 def test_capture_output():
79 def test_capture_output():
80 """capture_output() context works"""
80 """capture_output() context works"""
81
81
82 with capture_output() as io:
82 with capture_output() as io:
83 print('hi, stdout')
83 print('hi, stdout')
84 print('hi, stderr', file=sys.stderr)
84 print('hi, stderr', file=sys.stderr)
85
85
86 nt.assert_equal(io.stdout, 'hi, stdout\n')
86 nt.assert_equal(io.stdout, 'hi, stdout\n')
87 nt.assert_equal(io.stderr, 'hi, stderr\n')
87 nt.assert_equal(io.stderr, 'hi, stderr\n')
88
88
89 def test_UnicodeStdStream():
90 # Test wrapping a bytes-level stdout
91 if PY3:
92 stdoutb = stdlib_io.BytesIO()
93 stdout = stdlib_io.TextIOWrapper(stdoutb, encoding='ascii')
94 else:
95 stdout = stdoutb = stdlib_io.BytesIO()
96
97 orig_stdout = sys.stdout
98 sys.stdout = stdout
99 try:
100 sample = u"@Ε‚e¢ŧ←"
101 unicode_std_stream().write(sample)
102
103 output = stdoutb.getvalue().decode('utf-8')
104 nt.assert_equal(output, sample)
105 assert not stdout.closed
106 finally:
107 sys.stdout = orig_stdout
108
109 @skipif(not PY3, "Not applicable on Python 2")
110 def test_UnicodeStdStream_nowrap():
111 # If we replace stdout with a StringIO, it shouldn't get wrapped.
112 orig_stdout = sys.stdout
113 sys.stdout = StringIO()
114 try:
115 nt.assert_is(unicode_std_stream(), sys.stdout)
116 assert not sys.stdout.closed
117 finally:
118 sys.stdout = orig_stdout
119
89
120 def test_atomic_writing():
90 def test_atomic_writing():
121 class CustomExc(Exception): pass
91 class CustomExc(Exception): pass
122
92
123 with TemporaryDirectory() as td:
93 with TemporaryDirectory() as td:
124 f1 = os.path.join(td, 'penguin')
94 f1 = os.path.join(td, 'penguin')
125 with stdlib_io.open(f1, 'w') as f:
95 with stdlib_io.open(f1, 'w') as f:
126 f.write(u'Before')
96 f.write(u'Before')
127
97
128 if os.name != 'nt':
98 if os.name != 'nt':
129 os.chmod(f1, 0o701)
99 os.chmod(f1, 0o701)
130 orig_mode = stat.S_IMODE(os.stat(f1).st_mode)
100 orig_mode = stat.S_IMODE(os.stat(f1).st_mode)
131
101
132 f2 = os.path.join(td, 'flamingo')
102 f2 = os.path.join(td, 'flamingo')
133 try:
103 try:
134 os.symlink(f1, f2)
104 os.symlink(f1, f2)
135 have_symlink = True
105 have_symlink = True
136 except (AttributeError, NotImplementedError, OSError):
106 except (AttributeError, NotImplementedError, OSError):
137 # AttributeError: Python doesn't support it
107 # AttributeError: Python doesn't support it
138 # NotImplementedError: The system doesn't support it
108 # NotImplementedError: The system doesn't support it
139 # OSError: The user lacks the privilege (Windows)
109 # OSError: The user lacks the privilege (Windows)
140 have_symlink = False
110 have_symlink = False
141
111
142 with nt.assert_raises(CustomExc):
112 with nt.assert_raises(CustomExc):
143 with atomic_writing(f1) as f:
113 with atomic_writing(f1) as f:
144 f.write(u'Failing write')
114 f.write(u'Failing write')
145 raise CustomExc
115 raise CustomExc
146
116
147 # Because of the exception, the file should not have been modified
117 # Because of the exception, the file should not have been modified
148 with stdlib_io.open(f1, 'r') as f:
118 with stdlib_io.open(f1, 'r') as f:
149 nt.assert_equal(f.read(), u'Before')
119 nt.assert_equal(f.read(), u'Before')
150
120
151 with atomic_writing(f1) as f:
121 with atomic_writing(f1) as f:
152 f.write(u'Overwritten')
122 f.write(u'Overwritten')
153
123
154 with stdlib_io.open(f1, 'r') as f:
124 with stdlib_io.open(f1, 'r') as f:
155 nt.assert_equal(f.read(), u'Overwritten')
125 nt.assert_equal(f.read(), u'Overwritten')
156
126
157 if os.name != 'nt':
127 if os.name != 'nt':
158 mode = stat.S_IMODE(os.stat(f1).st_mode)
128 mode = stat.S_IMODE(os.stat(f1).st_mode)
159 nt.assert_equal(mode, orig_mode)
129 nt.assert_equal(mode, orig_mode)
160
130
161 if have_symlink:
131 if have_symlink:
162 # Check that writing over a file preserves a symlink
132 # Check that writing over a file preserves a symlink
163 with atomic_writing(f2) as f:
133 with atomic_writing(f2) as f:
164 f.write(u'written from symlink')
134 f.write(u'written from symlink')
165
135
166 with stdlib_io.open(f1, 'r') as f:
136 with stdlib_io.open(f1, 'r') as f:
167 nt.assert_equal(f.read(), u'written from symlink')
137 nt.assert_equal(f.read(), u'written from symlink')
168
138
169 def _save_umask():
139 def _save_umask():
170 global umask
140 global umask
171 umask = os.umask(0)
141 umask = os.umask(0)
172 os.umask(umask)
142 os.umask(umask)
173
143
174 def _restore_umask():
144 def _restore_umask():
175 os.umask(umask)
145 os.umask(umask)
176
146
177 @skip_win32
147 @skip_win32
178 @nt.with_setup(_save_umask, _restore_umask)
148 @nt.with_setup(_save_umask, _restore_umask)
179 def test_atomic_writing_umask():
149 def test_atomic_writing_umask():
180 with TemporaryDirectory() as td:
150 with TemporaryDirectory() as td:
181 os.umask(0o022)
151 os.umask(0o022)
182 f1 = os.path.join(td, '1')
152 f1 = os.path.join(td, '1')
183 with atomic_writing(f1) as f:
153 with atomic_writing(f1) as f:
184 f.write(u'1')
154 f.write(u'1')
185 mode = stat.S_IMODE(os.stat(f1).st_mode)
155 mode = stat.S_IMODE(os.stat(f1).st_mode)
186 nt.assert_equal(mode, 0o644, '{:o} != 644'.format(mode))
156 nt.assert_equal(mode, 0o644, '{:o} != 644'.format(mode))
187
157
188 os.umask(0o057)
158 os.umask(0o057)
189 f2 = os.path.join(td, '2')
159 f2 = os.path.join(td, '2')
190 with atomic_writing(f2) as f:
160 with atomic_writing(f2) as f:
191 f.write(u'2')
161 f.write(u'2')
192 mode = stat.S_IMODE(os.stat(f2).st_mode)
162 mode = stat.S_IMODE(os.stat(f2).st_mode)
193 nt.assert_equal(mode, 0o620, '{:o} != 620'.format(mode))
163 nt.assert_equal(mode, 0o620, '{:o} != 620'.format(mode))
194
164
195
165
196 def test_atomic_writing_newlines():
166 def test_atomic_writing_newlines():
197 with TemporaryDirectory() as td:
167 with TemporaryDirectory() as td:
198 path = os.path.join(td, 'testfile')
168 path = os.path.join(td, 'testfile')
199
169
200 lf = u'a\nb\nc\n'
170 lf = u'a\nb\nc\n'
201 plat = lf.replace(u'\n', os.linesep)
171 plat = lf.replace(u'\n', os.linesep)
202 crlf = lf.replace(u'\n', u'\r\n')
172 crlf = lf.replace(u'\n', u'\r\n')
203
173
204 # test default
174 # test default
205 with stdlib_io.open(path, 'w') as f:
175 with stdlib_io.open(path, 'w') as f:
206 f.write(lf)
176 f.write(lf)
207 with stdlib_io.open(path, 'r', newline='') as f:
177 with stdlib_io.open(path, 'r', newline='') as f:
208 read = f.read()
178 read = f.read()
209 nt.assert_equal(read, plat)
179 nt.assert_equal(read, plat)
210
180
211 # test newline=LF
181 # test newline=LF
212 with stdlib_io.open(path, 'w', newline='\n') as f:
182 with stdlib_io.open(path, 'w', newline='\n') as f:
213 f.write(lf)
183 f.write(lf)
214 with stdlib_io.open(path, 'r', newline='') as f:
184 with stdlib_io.open(path, 'r', newline='') as f:
215 read = f.read()
185 read = f.read()
216 nt.assert_equal(read, lf)
186 nt.assert_equal(read, lf)
217
187
218 # test newline=CRLF
188 # test newline=CRLF
219 with atomic_writing(path, newline='\r\n') as f:
189 with atomic_writing(path, newline='\r\n') as f:
220 f.write(lf)
190 f.write(lf)
221 with stdlib_io.open(path, 'r', newline='') as f:
191 with stdlib_io.open(path, 'r', newline='') as f:
222 read = f.read()
192 read = f.read()
223 nt.assert_equal(read, crlf)
193 nt.assert_equal(read, crlf)
224
194
225 # test newline=no convert
195 # test newline=no convert
226 text = u'crlf\r\ncr\rlf\n'
196 text = u'crlf\r\ncr\rlf\n'
227 with atomic_writing(path, newline='') as f:
197 with atomic_writing(path, newline='') as f:
228 f.write(text)
198 f.write(text)
229 with stdlib_io.open(path, 'r', newline='') as f:
199 with stdlib_io.open(path, 'r', newline='') as f:
230 read = f.read()
200 read = f.read()
231 nt.assert_equal(read, text)
201 nt.assert_equal(read, text)
@@ -1,34 +1,23 b''
1 """
1 """
2 Contains Stdout writer
2 Contains Stdout writer
3 """
3 """
4 #-----------------------------------------------------------------------------
5 #Copyright (c) 2013, the IPython Development Team.
6 #
7 #Distributed under the terms of the Modified BSD License.
8 #
9 #The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
11
4
12 #-----------------------------------------------------------------------------
5 # Copyright (c) Jupyter Development Team.
13 # Imports
6 # Distributed under the terms of the Modified BSD License.
14 #-----------------------------------------------------------------------------
15
7
16 from IPython.utils import io
8 from jupyter_nbconvert.utils import io
17 from .base import WriterBase
9 from .base import WriterBase
18
10
19 #-----------------------------------------------------------------------------
20 # Classes
21 #-----------------------------------------------------------------------------
22
11
23 class StdoutWriter(WriterBase):
12 class StdoutWriter(WriterBase):
24 """Consumes output from nbconvert export...() methods and writes to the
13 """Consumes output from nbconvert export...() methods and writes to the
25 stdout stream."""
14 stdout stream."""
26
15
27
16
28 def write(self, output, resources, **kw):
17 def write(self, output, resources, **kw):
29 """
18 """
30 Consume and write Jinja output.
19 Consume and write Jinja output.
31
20
32 See base for more...
21 See base for more...
33 """
22 """
34 io.unicode_std_stream().write(output)
23 io.unicode_std_stream().write(output)
General Comments 0
You need to be logged in to leave comments. Login now