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