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