##// END OF EJS Templates
Fixes to utils.io.Tee
Thomas Kluyver -
Show More
@@ -1,321 +1,321 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 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2009 The IPython Development Team
7 # Copyright (C) 2008-2009 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 from __future__ import print_function
12 from __future__ import print_function
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Code
21 # Code
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24
24
25 class IOStream:
25 class IOStream:
26
26
27 def __init__(self,stream, fallback=None):
27 def __init__(self,stream, fallback=None):
28 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
28 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
29 if fallback is not None:
29 if fallback is not None:
30 stream = fallback
30 stream = fallback
31 else:
31 else:
32 raise ValueError("fallback required, but not specified")
32 raise ValueError("fallback required, but not specified")
33 self.stream = stream
33 self.stream = stream
34 self._swrite = stream.write
34 self._swrite = stream.write
35
35
36 # clone all methods not overridden:
36 # clone all methods not overridden:
37 def clone(meth):
37 def clone(meth):
38 return not hasattr(self, meth) and not meth.startswith('_')
38 return not hasattr(self, meth) and not meth.startswith('_')
39 for meth in filter(clone, dir(stream)):
39 for meth in filter(clone, dir(stream)):
40 setattr(self, meth, getattr(stream, meth))
40 setattr(self, meth, getattr(stream, meth))
41
41
42 def write(self,data):
42 def write(self,data):
43 try:
43 try:
44 self._swrite(data)
44 self._swrite(data)
45 except:
45 except:
46 try:
46 try:
47 # print handles some unicode issues which may trip a plain
47 # print handles some unicode issues which may trip a plain
48 # write() call. Emulate write() by using an empty end
48 # write() call. Emulate write() by using an empty end
49 # argument.
49 # argument.
50 print(data, end='', file=self.stream)
50 print(data, end='', file=self.stream)
51 except:
51 except:
52 # if we get here, something is seriously broken.
52 # if we get here, something is seriously broken.
53 print('ERROR - failed to write data to stream:', self.stream,
53 print('ERROR - failed to write data to stream:', self.stream,
54 file=sys.stderr)
54 file=sys.stderr)
55
55
56 def writelines(self, lines):
56 def writelines(self, lines):
57 if isinstance(lines, basestring):
57 if isinstance(lines, basestring):
58 lines = [lines]
58 lines = [lines]
59 for line in lines:
59 for line in lines:
60 self.write(line)
60 self.write(line)
61
61
62 # This class used to have a writeln method, but regular files and streams
62 # This class used to have a writeln method, but regular files and streams
63 # in Python don't have this method. We need to keep this completely
63 # in Python don't have this method. We need to keep this completely
64 # compatible so we removed it.
64 # compatible so we removed it.
65
65
66 @property
66 @property
67 def closed(self):
67 def closed(self):
68 return self.stream.closed
68 return self.stream.closed
69
69
70 def close(self):
70 def close(self):
71 pass
71 pass
72
72
73
73
74 class IOTerm:
74 class IOTerm:
75 """ Term holds the file or file-like objects for handling I/O operations.
75 """ Term holds the file or file-like objects for handling I/O operations.
76
76
77 These are normally just sys.stdin, sys.stdout and sys.stderr but for
77 These are normally just sys.stdin, sys.stdout and sys.stderr but for
78 Windows they can can replaced to allow editing the strings before they are
78 Windows they can can replaced to allow editing the strings before they are
79 displayed."""
79 displayed."""
80
80
81 # In the future, having IPython channel all its I/O operations through
81 # In the future, having IPython channel all its I/O operations through
82 # this class will make it easier to embed it into other environments which
82 # this class will make it easier to embed it into other environments which
83 # are not a normal terminal (such as a GUI-based shell)
83 # are not a normal terminal (such as a GUI-based shell)
84 def __init__(self, stdin=None, stdout=None, stderr=None):
84 def __init__(self, stdin=None, stdout=None, stderr=None):
85 self.stdin = IOStream(stdin, sys.stdin)
85 self.stdin = IOStream(stdin, sys.stdin)
86 self.stdout = IOStream(stdout, sys.stdout)
86 self.stdout = IOStream(stdout, sys.stdout)
87 self.stderr = IOStream(stderr, sys.stderr)
87 self.stderr = IOStream(stderr, sys.stderr)
88
88
89 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
89 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
90 stdin = IOStream(sys.stdin)
90 stdin = IOStream(sys.stdin)
91 stdout = IOStream(sys.stdout)
91 stdout = IOStream(sys.stdout)
92 stderr = IOStream(sys.stderr)
92 stderr = IOStream(sys.stderr)
93
93
94
94
95 class Tee(object):
95 class Tee(object):
96 """A class to duplicate an output stream to stdout/err.
96 """A class to duplicate an output stream to stdout/err.
97
97
98 This works in a manner very similar to the Unix 'tee' command.
98 This works in a manner very similar to the Unix 'tee' command.
99
99
100 When the object is closed or deleted, it closes the original file given to
100 When the object is closed or deleted, it closes the original file given to
101 it for duplication.
101 it for duplication.
102 """
102 """
103 # Inspired by:
103 # Inspired by:
104 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
104 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
105
105
106 def __init__(self, file_or_name, mode=None, channel='stdout'):
106 def __init__(self, file_or_name, mode="w", channel='stdout'):
107 """Construct a new Tee object.
107 """Construct a new Tee object.
108
108
109 Parameters
109 Parameters
110 ----------
110 ----------
111 file_or_name : filename or open filehandle (writable)
111 file_or_name : filename or open filehandle (writable)
112 File that will be duplicated
112 File that will be duplicated
113
113
114 mode : optional, valid mode for open().
114 mode : optional, valid mode for open().
115 If a filename was give, open with this mode.
115 If a filename was give, open with this mode.
116
116
117 channel : str, one of ['stdout', 'stderr']
117 channel : str, one of ['stdout', 'stderr']
118 """
118 """
119 if channel not in ['stdout', 'stderr']:
119 if channel not in ['stdout', 'stderr']:
120 raise ValueError('Invalid channel spec %s' % channel)
120 raise ValueError('Invalid channel spec %s' % channel)
121
121
122 if hasattr(file, 'write') and hasattr(file, 'seek'):
122 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
123 self.file = file_or_name
123 self.file = file_or_name
124 else:
124 else:
125 self.file = open(file_or_name, mode)
125 self.file = open(file_or_name, mode)
126 self.channel = channel
126 self.channel = channel
127 self.ostream = getattr(sys, channel)
127 self.ostream = getattr(sys, channel)
128 setattr(sys, channel, self)
128 setattr(sys, channel, self)
129 self._closed = False
129 self._closed = False
130
130
131 def close(self):
131 def close(self):
132 """Close the file and restore the channel."""
132 """Close the file and restore the channel."""
133 self.flush()
133 self.flush()
134 setattr(sys, self.channel, self.ostream)
134 setattr(sys, self.channel, self.ostream)
135 self.file.close()
135 self.file.close()
136 self._closed = True
136 self._closed = True
137
137
138 def write(self, data):
138 def write(self, data):
139 """Write data to both channels."""
139 """Write data to both channels."""
140 self.file.write(data)
140 self.file.write(data)
141 self.ostream.write(data)
141 self.ostream.write(data)
142 self.ostream.flush()
142 self.ostream.flush()
143
143
144 def flush(self):
144 def flush(self):
145 """Flush both channels."""
145 """Flush both channels."""
146 self.file.flush()
146 self.file.flush()
147 self.ostream.flush()
147 self.ostream.flush()
148
148
149 def __del__(self):
149 def __del__(self):
150 if not self._closed:
150 if not self._closed:
151 self.close()
151 self.close()
152
152
153
153
154 def file_read(filename):
154 def file_read(filename):
155 """Read a file and close it. Returns the file source."""
155 """Read a file and close it. Returns the file source."""
156 fobj = open(filename,'r');
156 fobj = open(filename,'r');
157 source = fobj.read();
157 source = fobj.read();
158 fobj.close()
158 fobj.close()
159 return source
159 return source
160
160
161
161
162 def file_readlines(filename):
162 def file_readlines(filename):
163 """Read a file and close it. Returns the file source using readlines()."""
163 """Read a file and close it. Returns the file source using readlines()."""
164 fobj = open(filename,'r');
164 fobj = open(filename,'r');
165 lines = fobj.readlines();
165 lines = fobj.readlines();
166 fobj.close()
166 fobj.close()
167 return lines
167 return lines
168
168
169
169
170 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
170 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
171 """Take multiple lines of input.
171 """Take multiple lines of input.
172
172
173 A list with each line of input as a separate element is returned when a
173 A list with each line of input as a separate element is returned when a
174 termination string is entered (defaults to a single '.'). Input can also
174 termination string is entered (defaults to a single '.'). Input can also
175 terminate via EOF (^D in Unix, ^Z-RET in Windows).
175 terminate via EOF (^D in Unix, ^Z-RET in Windows).
176
176
177 Lines of input which end in \\ are joined into single entries (and a
177 Lines of input which end in \\ are joined into single entries (and a
178 secondary continuation prompt is issued as long as the user terminates
178 secondary continuation prompt is issued as long as the user terminates
179 lines with \\). This allows entering very long strings which are still
179 lines with \\). This allows entering very long strings which are still
180 meant to be treated as single entities.
180 meant to be treated as single entities.
181 """
181 """
182
182
183 try:
183 try:
184 if header:
184 if header:
185 header += '\n'
185 header += '\n'
186 lines = [raw_input(header + ps1)]
186 lines = [raw_input(header + ps1)]
187 except EOFError:
187 except EOFError:
188 return []
188 return []
189 terminate = [terminate_str]
189 terminate = [terminate_str]
190 try:
190 try:
191 while lines[-1:] != terminate:
191 while lines[-1:] != terminate:
192 new_line = raw_input(ps1)
192 new_line = raw_input(ps1)
193 while new_line.endswith('\\'):
193 while new_line.endswith('\\'):
194 new_line = new_line[:-1] + raw_input(ps2)
194 new_line = new_line[:-1] + raw_input(ps2)
195 lines.append(new_line)
195 lines.append(new_line)
196
196
197 return lines[:-1] # don't return the termination command
197 return lines[:-1] # don't return the termination command
198 except EOFError:
198 except EOFError:
199 print()
199 print()
200 return lines
200 return lines
201
201
202
202
203 def raw_input_ext(prompt='', ps2='... '):
203 def raw_input_ext(prompt='', ps2='... '):
204 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
204 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
205
205
206 line = raw_input(prompt)
206 line = raw_input(prompt)
207 while line.endswith('\\'):
207 while line.endswith('\\'):
208 line = line[:-1] + raw_input(ps2)
208 line = line[:-1] + raw_input(ps2)
209 return line
209 return line
210
210
211
211
212 def ask_yes_no(prompt,default=None):
212 def ask_yes_no(prompt,default=None):
213 """Asks a question and returns a boolean (y/n) answer.
213 """Asks a question and returns a boolean (y/n) answer.
214
214
215 If default is given (one of 'y','n'), it is used if the user input is
215 If default is given (one of 'y','n'), it is used if the user input is
216 empty. Otherwise the question is repeated until an answer is given.
216 empty. Otherwise the question is repeated until an answer is given.
217
217
218 An EOF is treated as the default answer. If there is no default, an
218 An EOF is treated as the default answer. If there is no default, an
219 exception is raised to prevent infinite loops.
219 exception is raised to prevent infinite loops.
220
220
221 Valid answers are: y/yes/n/no (match is not case sensitive)."""
221 Valid answers are: y/yes/n/no (match is not case sensitive)."""
222
222
223 answers = {'y':True,'n':False,'yes':True,'no':False}
223 answers = {'y':True,'n':False,'yes':True,'no':False}
224 ans = None
224 ans = None
225 while ans not in answers.keys():
225 while ans not in answers.keys():
226 try:
226 try:
227 ans = raw_input(prompt+' ').lower()
227 ans = raw_input(prompt+' ').lower()
228 if not ans: # response was an empty string
228 if not ans: # response was an empty string
229 ans = default
229 ans = default
230 except KeyboardInterrupt:
230 except KeyboardInterrupt:
231 pass
231 pass
232 except EOFError:
232 except EOFError:
233 if default in answers.keys():
233 if default in answers.keys():
234 ans = default
234 ans = default
235 print()
235 print()
236 else:
236 else:
237 raise
237 raise
238
238
239 return answers[ans]
239 return answers[ans]
240
240
241
241
242 class NLprinter:
242 class NLprinter:
243 """Print an arbitrarily nested list, indicating index numbers.
243 """Print an arbitrarily nested list, indicating index numbers.
244
244
245 An instance of this class called nlprint is available and callable as a
245 An instance of this class called nlprint is available and callable as a
246 function.
246 function.
247
247
248 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
248 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
249 and using 'sep' to separate the index from the value. """
249 and using 'sep' to separate the index from the value. """
250
250
251 def __init__(self):
251 def __init__(self):
252 self.depth = 0
252 self.depth = 0
253
253
254 def __call__(self,lst,pos='',**kw):
254 def __call__(self,lst,pos='',**kw):
255 """Prints the nested list numbering levels."""
255 """Prints the nested list numbering levels."""
256 kw.setdefault('indent',' ')
256 kw.setdefault('indent',' ')
257 kw.setdefault('sep',': ')
257 kw.setdefault('sep',': ')
258 kw.setdefault('start',0)
258 kw.setdefault('start',0)
259 kw.setdefault('stop',len(lst))
259 kw.setdefault('stop',len(lst))
260 # we need to remove start and stop from kw so they don't propagate
260 # we need to remove start and stop from kw so they don't propagate
261 # into a recursive call for a nested list.
261 # into a recursive call for a nested list.
262 start = kw['start']; del kw['start']
262 start = kw['start']; del kw['start']
263 stop = kw['stop']; del kw['stop']
263 stop = kw['stop']; del kw['stop']
264 if self.depth == 0 and 'header' in kw.keys():
264 if self.depth == 0 and 'header' in kw.keys():
265 print(kw['header'])
265 print(kw['header'])
266
266
267 for idx in range(start,stop):
267 for idx in range(start,stop):
268 elem = lst[idx]
268 elem = lst[idx]
269 newpos = pos + str(idx)
269 newpos = pos + str(idx)
270 if type(elem)==type([]):
270 if type(elem)==type([]):
271 self.depth += 1
271 self.depth += 1
272 self.__call__(elem, newpos+",", **kw)
272 self.__call__(elem, newpos+",", **kw)
273 self.depth -= 1
273 self.depth -= 1
274 else:
274 else:
275 print(kw['indent']*self.depth + newpos + kw["sep"] + repr(elem))
275 print(kw['indent']*self.depth + newpos + kw["sep"] + repr(elem))
276
276
277 nlprint = NLprinter()
277 nlprint = NLprinter()
278
278
279
279
280 def temp_pyfile(src, ext='.py'):
280 def temp_pyfile(src, ext='.py'):
281 """Make a temporary python file, return filename and filehandle.
281 """Make a temporary python file, return filename and filehandle.
282
282
283 Parameters
283 Parameters
284 ----------
284 ----------
285 src : string or list of strings (no need for ending newlines if list)
285 src : string or list of strings (no need for ending newlines if list)
286 Source code to be written to the file.
286 Source code to be written to the file.
287
287
288 ext : optional, string
288 ext : optional, string
289 Extension for the generated file.
289 Extension for the generated file.
290
290
291 Returns
291 Returns
292 -------
292 -------
293 (filename, open filehandle)
293 (filename, open filehandle)
294 It is the caller's responsibility to close the open file and unlink it.
294 It is the caller's responsibility to close the open file and unlink it.
295 """
295 """
296 fname = tempfile.mkstemp(ext)[1]
296 fname = tempfile.mkstemp(ext)[1]
297 f = open(fname,'w')
297 f = open(fname,'w')
298 f.write(src)
298 f.write(src)
299 f.flush()
299 f.flush()
300 return fname, f
300 return fname, f
301
301
302
302
303 def raw_print(*args, **kw):
303 def raw_print(*args, **kw):
304 """Raw print to sys.__stdout__, otherwise identical interface to print()."""
304 """Raw print to sys.__stdout__, otherwise identical interface to print()."""
305
305
306 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
306 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
307 file=sys.__stdout__)
307 file=sys.__stdout__)
308 sys.__stdout__.flush()
308 sys.__stdout__.flush()
309
309
310
310
311 def raw_print_err(*args, **kw):
311 def raw_print_err(*args, **kw):
312 """Raw print to sys.__stderr__, otherwise identical interface to print()."""
312 """Raw print to sys.__stderr__, otherwise identical interface to print()."""
313
313
314 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
314 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
315 file=sys.__stderr__)
315 file=sys.__stderr__)
316 sys.__stderr__.flush()
316 sys.__stderr__.flush()
317
317
318
318
319 # Short aliases for quick debugging, do NOT use these in production code.
319 # Short aliases for quick debugging, do NOT use these in production code.
320 rprint = raw_print
320 rprint = raw_print
321 rprinte = raw_print_err
321 rprinte = raw_print_err
General Comments 0
You need to be logged in to leave comments. Login now