##// END OF EJS Templates
custom keyboard interrupt handling in ask_yes_no...
Paul Ivanov -
Show More
@@ -1,229 +1,232 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-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 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 os
17 import os
18 import sys
18 import sys
19 import tempfile
19 import tempfile
20 from .capture import CapturedIO, capture_output
20 from .capture import CapturedIO, capture_output
21 from .py3compat import string_types, input
21 from .py3compat import string_types, input
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Code
24 # Code
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27
27
28 class IOStream:
28 class IOStream:
29
29
30 def __init__(self,stream, fallback=None):
30 def __init__(self,stream, fallback=None):
31 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
31 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
32 if fallback is not None:
32 if fallback is not None:
33 stream = fallback
33 stream = fallback
34 else:
34 else:
35 raise ValueError("fallback required, but not specified")
35 raise ValueError("fallback required, but not specified")
36 self.stream = stream
36 self.stream = stream
37 self._swrite = stream.write
37 self._swrite = stream.write
38
38
39 # clone all methods not overridden:
39 # clone all methods not overridden:
40 def clone(meth):
40 def clone(meth):
41 return not hasattr(self, meth) and not meth.startswith('_')
41 return not hasattr(self, meth) and not meth.startswith('_')
42 for meth in filter(clone, dir(stream)):
42 for meth in filter(clone, dir(stream)):
43 setattr(self, meth, getattr(stream, meth))
43 setattr(self, meth, getattr(stream, meth))
44
44
45 def write(self,data):
45 def write(self,data):
46 try:
46 try:
47 self._swrite(data)
47 self._swrite(data)
48 except:
48 except:
49 try:
49 try:
50 # print handles some unicode issues which may trip a plain
50 # print handles some unicode issues which may trip a plain
51 # write() call. Emulate write() by using an empty end
51 # write() call. Emulate write() by using an empty end
52 # argument.
52 # argument.
53 print(data, end='', file=self.stream)
53 print(data, end='', file=self.stream)
54 except:
54 except:
55 # if we get here, something is seriously broken.
55 # if we get here, something is seriously broken.
56 print('ERROR - failed to write data to stream:', self.stream,
56 print('ERROR - failed to write data to stream:', self.stream,
57 file=sys.stderr)
57 file=sys.stderr)
58
58
59 def writelines(self, lines):
59 def writelines(self, lines):
60 if isinstance(lines, string_types):
60 if isinstance(lines, string_types):
61 lines = [lines]
61 lines = [lines]
62 for line in lines:
62 for line in lines:
63 self.write(line)
63 self.write(line)
64
64
65 # This class used to have a writeln method, but regular files and streams
65 # This class used to have a writeln method, but regular files and streams
66 # in Python don't have this method. We need to keep this completely
66 # in Python don't have this method. We need to keep this completely
67 # compatible so we removed it.
67 # compatible so we removed it.
68
68
69 @property
69 @property
70 def closed(self):
70 def closed(self):
71 return self.stream.closed
71 return self.stream.closed
72
72
73 def close(self):
73 def close(self):
74 pass
74 pass
75
75
76 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
76 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
77 devnull = open(os.devnull, 'a')
77 devnull = open(os.devnull, 'a')
78 stdin = IOStream(sys.stdin, fallback=devnull)
78 stdin = IOStream(sys.stdin, fallback=devnull)
79 stdout = IOStream(sys.stdout, fallback=devnull)
79 stdout = IOStream(sys.stdout, fallback=devnull)
80 stderr = IOStream(sys.stderr, fallback=devnull)
80 stderr = IOStream(sys.stderr, fallback=devnull)
81
81
82 class IOTerm:
82 class IOTerm:
83 """ Term holds the file or file-like objects for handling I/O operations.
83 """ Term holds the file or file-like objects for handling I/O operations.
84
84
85 These are normally just sys.stdin, sys.stdout and sys.stderr but for
85 These are normally just sys.stdin, sys.stdout and sys.stderr but for
86 Windows they can can replaced to allow editing the strings before they are
86 Windows they can can replaced to allow editing the strings before they are
87 displayed."""
87 displayed."""
88
88
89 # In the future, having IPython channel all its I/O operations through
89 # In the future, having IPython channel all its I/O operations through
90 # this class will make it easier to embed it into other environments which
90 # this class will make it easier to embed it into other environments which
91 # are not a normal terminal (such as a GUI-based shell)
91 # are not a normal terminal (such as a GUI-based shell)
92 def __init__(self, stdin=None, stdout=None, stderr=None):
92 def __init__(self, stdin=None, stdout=None, stderr=None):
93 mymodule = sys.modules[__name__]
93 mymodule = sys.modules[__name__]
94 self.stdin = IOStream(stdin, mymodule.stdin)
94 self.stdin = IOStream(stdin, mymodule.stdin)
95 self.stdout = IOStream(stdout, mymodule.stdout)
95 self.stdout = IOStream(stdout, mymodule.stdout)
96 self.stderr = IOStream(stderr, mymodule.stderr)
96 self.stderr = IOStream(stderr, mymodule.stderr)
97
97
98
98
99 class Tee(object):
99 class Tee(object):
100 """A class to duplicate an output stream to stdout/err.
100 """A class to duplicate an output stream to stdout/err.
101
101
102 This works in a manner very similar to the Unix 'tee' command.
102 This works in a manner very similar to the Unix 'tee' command.
103
103
104 When the object is closed or deleted, it closes the original file given to
104 When the object is closed or deleted, it closes the original file given to
105 it for duplication.
105 it for duplication.
106 """
106 """
107 # Inspired by:
107 # Inspired by:
108 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
108 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
109
109
110 def __init__(self, file_or_name, mode="w", channel='stdout'):
110 def __init__(self, file_or_name, mode="w", channel='stdout'):
111 """Construct a new Tee object.
111 """Construct a new Tee object.
112
112
113 Parameters
113 Parameters
114 ----------
114 ----------
115 file_or_name : filename or open filehandle (writable)
115 file_or_name : filename or open filehandle (writable)
116 File that will be duplicated
116 File that will be duplicated
117
117
118 mode : optional, valid mode for open().
118 mode : optional, valid mode for open().
119 If a filename was give, open with this mode.
119 If a filename was give, open with this mode.
120
120
121 channel : str, one of ['stdout', 'stderr']
121 channel : str, one of ['stdout', 'stderr']
122 """
122 """
123 if channel not in ['stdout', 'stderr']:
123 if channel not in ['stdout', 'stderr']:
124 raise ValueError('Invalid channel spec %s' % channel)
124 raise ValueError('Invalid channel spec %s' % channel)
125
125
126 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
126 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
127 self.file = file_or_name
127 self.file = file_or_name
128 else:
128 else:
129 self.file = open(file_or_name, mode)
129 self.file = open(file_or_name, mode)
130 self.channel = channel
130 self.channel = channel
131 self.ostream = getattr(sys, channel)
131 self.ostream = getattr(sys, channel)
132 setattr(sys, channel, self)
132 setattr(sys, channel, self)
133 self._closed = False
133 self._closed = False
134
134
135 def close(self):
135 def close(self):
136 """Close the file and restore the channel."""
136 """Close the file and restore the channel."""
137 self.flush()
137 self.flush()
138 setattr(sys, self.channel, self.ostream)
138 setattr(sys, self.channel, self.ostream)
139 self.file.close()
139 self.file.close()
140 self._closed = True
140 self._closed = True
141
141
142 def write(self, data):
142 def write(self, data):
143 """Write data to both channels."""
143 """Write data to both channels."""
144 self.file.write(data)
144 self.file.write(data)
145 self.ostream.write(data)
145 self.ostream.write(data)
146 self.ostream.flush()
146 self.ostream.flush()
147
147
148 def flush(self):
148 def flush(self):
149 """Flush both channels."""
149 """Flush both channels."""
150 self.file.flush()
150 self.file.flush()
151 self.ostream.flush()
151 self.ostream.flush()
152
152
153 def __del__(self):
153 def __del__(self):
154 if not self._closed:
154 if not self._closed:
155 self.close()
155 self.close()
156
156
157
157
158 def ask_yes_no(prompt,default=None):
158 def ask_yes_no(prompt, default=None, interrupt=None):
159 """Asks a question and returns a boolean (y/n) answer.
159 """Asks a question and returns a boolean (y/n) answer.
160
160
161 If default is given (one of 'y','n'), it is used if the user input is
161 If default is given (one of 'y','n'), it is used if the user input is
162 empty. Otherwise the question is repeated until an answer is given.
162 empty. If interrupt is given (one of 'y','n'), it is used if the user
163 presses Ctrl-C. Otherwise the question is repeated until an answer is
164 given.
163
165
164 An EOF is treated as the default answer. If there is no default, an
166 An EOF is treated as the default answer. If there is no default, an
165 exception is raised to prevent infinite loops.
167 exception is raised to prevent infinite loops.
166
168
167 Valid answers are: y/yes/n/no (match is not case sensitive)."""
169 Valid answers are: y/yes/n/no (match is not case sensitive)."""
168
170
169 answers = {'y':True,'n':False,'yes':True,'no':False}
171 answers = {'y':True,'n':False,'yes':True,'no':False}
170 ans = None
172 ans = None
171 while ans not in answers.keys():
173 while ans not in answers.keys():
172 try:
174 try:
173 ans = input(prompt+' ').lower()
175 ans = input(prompt+' ').lower()
174 if not ans: # response was an empty string
176 if not ans: # response was an empty string
175 ans = default
177 ans = default
176 except KeyboardInterrupt:
178 except KeyboardInterrupt:
177 pass
179 if interrupt:
180 ans = interrupt
178 except EOFError:
181 except EOFError:
179 if default in answers.keys():
182 if default in answers.keys():
180 ans = default
183 ans = default
181 print()
184 print()
182 else:
185 else:
183 raise
186 raise
184
187
185 return answers[ans]
188 return answers[ans]
186
189
187
190
188 def temp_pyfile(src, ext='.py'):
191 def temp_pyfile(src, ext='.py'):
189 """Make a temporary python file, return filename and filehandle.
192 """Make a temporary python file, return filename and filehandle.
190
193
191 Parameters
194 Parameters
192 ----------
195 ----------
193 src : string or list of strings (no need for ending newlines if list)
196 src : string or list of strings (no need for ending newlines if list)
194 Source code to be written to the file.
197 Source code to be written to the file.
195
198
196 ext : optional, string
199 ext : optional, string
197 Extension for the generated file.
200 Extension for the generated file.
198
201
199 Returns
202 Returns
200 -------
203 -------
201 (filename, open filehandle)
204 (filename, open filehandle)
202 It is the caller's responsibility to close the open file and unlink it.
205 It is the caller's responsibility to close the open file and unlink it.
203 """
206 """
204 fname = tempfile.mkstemp(ext)[1]
207 fname = tempfile.mkstemp(ext)[1]
205 f = open(fname,'w')
208 f = open(fname,'w')
206 f.write(src)
209 f.write(src)
207 f.flush()
210 f.flush()
208 return fname, f
211 return fname, f
209
212
210
213
211 def raw_print(*args, **kw):
214 def raw_print(*args, **kw):
212 """Raw print to sys.__stdout__, otherwise identical interface to print()."""
215 """Raw print to sys.__stdout__, otherwise identical interface to print()."""
213
216
214 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
217 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
215 file=sys.__stdout__)
218 file=sys.__stdout__)
216 sys.__stdout__.flush()
219 sys.__stdout__.flush()
217
220
218
221
219 def raw_print_err(*args, **kw):
222 def raw_print_err(*args, **kw):
220 """Raw print to sys.__stderr__, otherwise identical interface to print()."""
223 """Raw print to sys.__stderr__, otherwise identical interface to print()."""
221
224
222 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
225 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
223 file=sys.__stderr__)
226 file=sys.__stderr__)
224 sys.__stderr__.flush()
227 sys.__stderr__.flush()
225
228
226
229
227 # Short aliases for quick debugging, do NOT use these in production code.
230 # Short aliases for quick debugging, do NOT use these in production code.
228 rprint = raw_print
231 rprint = raw_print
229 rprinte = raw_print_err
232 rprinte = raw_print_err
General Comments 0
You need to be logged in to leave comments. Login now