##// END OF EJS Templates
Update irunner to work in Python 3.
Thomas Kluyver -
Show More
@@ -1,440 +1,442
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Module for interactively running scripts.
2 """Module for interactively running scripts.
3
3
4 This module implements classes for interactively running scripts written for
4 This module implements classes for interactively running scripts written for
5 any system with a prompt which can be matched by a regexp suitable for
5 any system with a prompt which can be matched by a regexp suitable for
6 pexpect. It can be used to run as if they had been typed up interactively, an
6 pexpect. It can be used to run as if they had been typed up interactively, an
7 arbitrary series of commands for the target system.
7 arbitrary series of commands for the target system.
8
8
9 The module includes classes ready for IPython (with the default prompts),
9 The module includes classes ready for IPython (with the default prompts),
10 plain Python and SAGE, but making a new one is trivial. To see how to use it,
10 plain Python and SAGE, but making a new one is trivial. To see how to use it,
11 simply run the module as a script:
11 simply run the module as a script:
12
12
13 ./irunner.py --help
13 ./irunner.py --help
14
14
15
15
16 This is an extension of Ken Schutte <kschutte-AT-csail.mit.edu>'s script
16 This is an extension of Ken Schutte <kschutte-AT-csail.mit.edu>'s script
17 contributed on the ipython-user list:
17 contributed on the ipython-user list:
18
18
19 http://mail.scipy.org/pipermail/ipython-user/2006-May/003539.html
19 http://mail.scipy.org/pipermail/ipython-user/2006-May/003539.html
20
20
21
21
22 NOTES:
22 NOTES:
23
23
24 - This module requires pexpect, available in most linux distros, or which can
24 - This module requires pexpect, available in most linux distros, or which can
25 be downloaded from
25 be downloaded from
26
26
27 http://pexpect.sourceforge.net
27 http://pexpect.sourceforge.net
28
28
29 - Because pexpect only works under Unix or Windows-Cygwin, this has the same
29 - Because pexpect only works under Unix or Windows-Cygwin, this has the same
30 limitations. This means that it will NOT work under native windows Python.
30 limitations. This means that it will NOT work under native windows Python.
31 """
31 """
32
32
33 # Stdlib imports
33 # Stdlib imports
34 import optparse
34 import optparse
35 import os
35 import os
36 import sys
36 import sys
37
37
38 # Third-party modules: we carry a copy of pexpect to reduce the need for
38 # Third-party modules: we carry a copy of pexpect to reduce the need for
39 # external dependencies, but our import checks for a system version first.
39 # external dependencies, but our import checks for a system version first.
40 from IPython.external import pexpect
40 from IPython.external import pexpect
41 from IPython.utils import py3compat
41
42
42 # Global usage strings, to avoid indentation issues when typing it below.
43 # Global usage strings, to avoid indentation issues when typing it below.
43 USAGE = """
44 USAGE = """
44 Interactive script runner, type: %s
45 Interactive script runner, type: %s
45
46
46 runner [opts] script_name
47 runner [opts] script_name
47 """
48 """
48
49
49 def pexpect_monkeypatch():
50 def pexpect_monkeypatch():
50 """Patch pexpect to prevent unhandled exceptions at VM teardown.
51 """Patch pexpect to prevent unhandled exceptions at VM teardown.
51
52
52 Calling this function will monkeypatch the pexpect.spawn class and modify
53 Calling this function will monkeypatch the pexpect.spawn class and modify
53 its __del__ method to make it more robust in the face of failures that can
54 its __del__ method to make it more robust in the face of failures that can
54 occur if it is called when the Python VM is shutting down.
55 occur if it is called when the Python VM is shutting down.
55
56
56 Since Python may fire __del__ methods arbitrarily late, it's possible for
57 Since Python may fire __del__ methods arbitrarily late, it's possible for
57 them to execute during the teardown of the Python VM itself. At this
58 them to execute during the teardown of the Python VM itself. At this
58 point, various builtin modules have been reset to None. Thus, the call to
59 point, various builtin modules have been reset to None. Thus, the call to
59 self.close() will trigger an exception because it tries to call os.close(),
60 self.close() will trigger an exception because it tries to call os.close(),
60 and os is now None.
61 and os is now None.
61 """
62 """
62
63
63 if pexpect.__version__[:3] >= '2.2':
64 if pexpect.__version__[:3] >= '2.2':
64 # No need to patch, fix is already the upstream version.
65 # No need to patch, fix is already the upstream version.
65 return
66 return
66
67
67 def __del__(self):
68 def __del__(self):
68 """This makes sure that no system resources are left open.
69 """This makes sure that no system resources are left open.
69 Python only garbage collects Python objects. OS file descriptors
70 Python only garbage collects Python objects. OS file descriptors
70 are not Python objects, so they must be handled explicitly.
71 are not Python objects, so they must be handled explicitly.
71 If the child file descriptor was opened outside of this class
72 If the child file descriptor was opened outside of this class
72 (passed to the constructor) then this does not close it.
73 (passed to the constructor) then this does not close it.
73 """
74 """
74 if not self.closed:
75 if not self.closed:
75 try:
76 try:
76 self.close()
77 self.close()
77 except AttributeError:
78 except AttributeError:
78 pass
79 pass
79
80
80 pexpect.spawn.__del__ = __del__
81 pexpect.spawn.__del__ = __del__
81
82
82 pexpect_monkeypatch()
83 pexpect_monkeypatch()
83
84
84 # The generic runner class
85 # The generic runner class
85 class InteractiveRunner(object):
86 class InteractiveRunner(object):
86 """Class to run a sequence of commands through an interactive program."""
87 """Class to run a sequence of commands through an interactive program."""
87
88
88 def __init__(self,program,prompts,args=None,out=sys.stdout,echo=True):
89 def __init__(self,program,prompts,args=None,out=sys.stdout,echo=True):
89 """Construct a runner.
90 """Construct a runner.
90
91
91 Inputs:
92 Inputs:
92
93
93 - program: command to execute the given program.
94 - program: command to execute the given program.
94
95
95 - prompts: a list of patterns to match as valid prompts, in the
96 - prompts: a list of patterns to match as valid prompts, in the
96 format used by pexpect. This basically means that it can be either
97 format used by pexpect. This basically means that it can be either
97 a string (to be compiled as a regular expression) or a list of such
98 a string (to be compiled as a regular expression) or a list of such
98 (it must be a true list, as pexpect does type checks).
99 (it must be a true list, as pexpect does type checks).
99
100
100 If more than one prompt is given, the first is treated as the main
101 If more than one prompt is given, the first is treated as the main
101 program prompt and the others as 'continuation' prompts, like
102 program prompt and the others as 'continuation' prompts, like
102 python's. This means that blank lines in the input source are
103 python's. This means that blank lines in the input source are
103 ommitted when the first prompt is matched, but are NOT ommitted when
104 ommitted when the first prompt is matched, but are NOT ommitted when
104 the continuation one matches, since this is how python signals the
105 the continuation one matches, since this is how python signals the
105 end of multiline input interactively.
106 end of multiline input interactively.
106
107
107 Optional inputs:
108 Optional inputs:
108
109
109 - args(None): optional list of strings to pass as arguments to the
110 - args(None): optional list of strings to pass as arguments to the
110 child program.
111 child program.
111
112
112 - out(sys.stdout): if given, an output stream to be used when writing
113 - out(sys.stdout): if given, an output stream to be used when writing
113 output. The only requirement is that it must have a .write() method.
114 output. The only requirement is that it must have a .write() method.
114
115
115 Public members not parameterized in the constructor:
116 Public members not parameterized in the constructor:
116
117
117 - delaybeforesend(0): Newer versions of pexpect have a delay before
118 - delaybeforesend(0): Newer versions of pexpect have a delay before
118 sending each new input. For our purposes here, it's typically best
119 sending each new input. For our purposes here, it's typically best
119 to just set this to zero, but if you encounter reliability problems
120 to just set this to zero, but if you encounter reliability problems
120 or want an interactive run to pause briefly at each prompt, just
121 or want an interactive run to pause briefly at each prompt, just
121 increase this value (it is measured in seconds). Note that this
122 increase this value (it is measured in seconds). Note that this
122 variable is not honored at all by older versions of pexpect.
123 variable is not honored at all by older versions of pexpect.
123 """
124 """
124
125
125 self.program = program
126 self.program = program
126 self.prompts = prompts
127 self.prompts = prompts
127 if args is None: args = []
128 if args is None: args = []
128 self.args = args
129 self.args = args
129 self.out = out
130 self.out = out
130 self.echo = echo
131 self.echo = echo
131 # Other public members which we don't make as parameters, but which
132 # Other public members which we don't make as parameters, but which
132 # users may occasionally want to tweak
133 # users may occasionally want to tweak
133 self.delaybeforesend = 0
134 self.delaybeforesend = 0
134
135
135 # Create child process and hold on to it so we don't have to re-create
136 # Create child process and hold on to it so we don't have to re-create
136 # for every single execution call
137 # for every single execution call
137 c = self.child = pexpect.spawn(self.program,self.args,timeout=None)
138 c = self.child = pexpect.spawn(self.program,self.args,timeout=None)
138 c.delaybeforesend = self.delaybeforesend
139 c.delaybeforesend = self.delaybeforesend
139 # pexpect hard-codes the terminal size as (24,80) (rows,columns).
140 # pexpect hard-codes the terminal size as (24,80) (rows,columns).
140 # This causes problems because any line longer than 80 characters gets
141 # This causes problems because any line longer than 80 characters gets
141 # completely overwrapped on the printed outptut (even though
142 # completely overwrapped on the printed outptut (even though
142 # internally the code runs fine). We reset this to 99 rows X 200
143 # internally the code runs fine). We reset this to 99 rows X 200
143 # columns (arbitrarily chosen), which should avoid problems in all
144 # columns (arbitrarily chosen), which should avoid problems in all
144 # reasonable cases.
145 # reasonable cases.
145 c.setwinsize(99,200)
146 c.setwinsize(99,200)
146
147
147 def close(self):
148 def close(self):
148 """close child process"""
149 """close child process"""
149
150
150 self.child.close()
151 self.child.close()
151
152
152 def run_file(self,fname,interact=False,get_output=False):
153 def run_file(self,fname,interact=False,get_output=False):
153 """Run the given file interactively.
154 """Run the given file interactively.
154
155
155 Inputs:
156 Inputs:
156
157
157 -fname: name of the file to execute.
158 -fname: name of the file to execute.
158
159
159 See the run_source docstring for the meaning of the optional
160 See the run_source docstring for the meaning of the optional
160 arguments."""
161 arguments."""
161
162
162 fobj = open(fname,'r')
163 fobj = open(fname,'r')
163 try:
164 try:
164 out = self.run_source(fobj,interact,get_output)
165 out = self.run_source(fobj,interact,get_output)
165 finally:
166 finally:
166 fobj.close()
167 fobj.close()
167 if get_output:
168 if get_output:
168 return out
169 return out
169
170
170 def run_source(self,source,interact=False,get_output=False):
171 def run_source(self,source,interact=False,get_output=False):
171 """Run the given source code interactively.
172 """Run the given source code interactively.
172
173
173 Inputs:
174 Inputs:
174
175
175 - source: a string of code to be executed, or an open file object we
176 - source: a string of code to be executed, or an open file object we
176 can iterate over.
177 can iterate over.
177
178
178 Optional inputs:
179 Optional inputs:
179
180
180 - interact(False): if true, start to interact with the running
181 - interact(False): if true, start to interact with the running
181 program at the end of the script. Otherwise, just exit.
182 program at the end of the script. Otherwise, just exit.
182
183
183 - get_output(False): if true, capture the output of the child process
184 - get_output(False): if true, capture the output of the child process
184 (filtering the input commands out) and return it as a string.
185 (filtering the input commands out) and return it as a string.
185
186
186 Returns:
187 Returns:
187 A string containing the process output, but only if requested.
188 A string containing the process output, but only if requested.
188 """
189 """
189
190
190 # if the source is a string, chop it up in lines so we can iterate
191 # if the source is a string, chop it up in lines so we can iterate
191 # over it just as if it were an open file.
192 # over it just as if it were an open file.
192 if isinstance(source, basestring):
193 if isinstance(source, basestring):
193 source = source.splitlines(True)
194 source = source.splitlines(True)
194
195
195 if self.echo:
196 if self.echo:
196 # normalize all strings we write to use the native OS line
197 # normalize all strings we write to use the native OS line
197 # separators.
198 # separators.
198 linesep = os.linesep
199 linesep = os.linesep
199 stdwrite = self.out.write
200 stdwrite = self.out.write
200 write = lambda s: stdwrite(s.replace('\r\n',linesep))
201 write = lambda s: stdwrite(s.replace('\r\n',linesep))
201 else:
202 else:
202 # Quiet mode, all writes are no-ops
203 # Quiet mode, all writes are no-ops
203 write = lambda s: None
204 write = lambda s: None
204
205
205 c = self.child
206 c = self.child
206 prompts = c.compile_pattern_list(self.prompts)
207 prompts = c.compile_pattern_list(self.prompts)
207 prompt_idx = c.expect_list(prompts)
208 prompt_idx = c.expect_list(prompts)
208
209
209 # Flag whether the script ends normally or not, to know whether we can
210 # Flag whether the script ends normally or not, to know whether we can
210 # do anything further with the underlying process.
211 # do anything further with the underlying process.
211 end_normal = True
212 end_normal = True
212
213
213 # If the output was requested, store it in a list for return at the end
214 # If the output was requested, store it in a list for return at the end
214 if get_output:
215 if get_output:
215 output = []
216 output = []
216 store_output = output.append
217 store_output = output.append
217
218
218 for cmd in source:
219 for cmd in source:
219 # skip blank lines for all matches to the 'main' prompt, while the
220 # skip blank lines for all matches to the 'main' prompt, while the
220 # secondary prompts do not
221 # secondary prompts do not
221 if prompt_idx==0 and \
222 if prompt_idx==0 and \
222 (cmd.isspace() or cmd.lstrip().startswith('#')):
223 (cmd.isspace() or cmd.lstrip().startswith('#')):
223 write(cmd)
224 write(cmd)
224 continue
225 continue
225
226
226 # write('AFTER: '+c.after) # dbg
227 # write('AFTER: '+c.after) # dbg
227 write(c.after)
228 write(c.after)
228 c.send(cmd)
229 c.send(cmd)
229 try:
230 try:
230 prompt_idx = c.expect_list(prompts)
231 prompt_idx = c.expect_list(prompts)
231 except pexpect.EOF:
232 except pexpect.EOF:
232 # this will happen if the child dies unexpectedly
233 # this will happen if the child dies unexpectedly
233 write(c.before)
234 write(c.before)
234 end_normal = False
235 end_normal = False
235 break
236 break
236
237
237 write(c.before)
238 write(c.before)
238
239
239 # With an echoing process, the output we get in c.before contains
240 # With an echoing process, the output we get in c.before contains
240 # the command sent, a newline, and then the actual process output
241 # the command sent, a newline, and then the actual process output
241 if get_output:
242 if get_output:
242 store_output(c.before[len(cmd+'\n'):])
243 store_output(c.before[len(cmd+'\n'):])
243 #write('CMD: <<%s>>' % cmd) # dbg
244 #write('CMD: <<%s>>' % cmd) # dbg
244 #write('OUTPUT: <<%s>>' % output[-1]) # dbg
245 #write('OUTPUT: <<%s>>' % output[-1]) # dbg
245
246
246 self.out.flush()
247 self.out.flush()
247 if end_normal:
248 if end_normal:
248 if interact:
249 if interact:
249 c.send('\n')
250 c.send('\n')
250 print '<< Starting interactive mode >>',
251 print '<< Starting interactive mode >>',
251 try:
252 try:
252 c.interact()
253 c.interact()
253 except OSError:
254 except OSError:
254 # This is what fires when the child stops. Simply print a
255 # This is what fires when the child stops. Simply print a
255 # newline so the system prompt is aligned. The extra
256 # newline so the system prompt is aligned. The extra
256 # space is there to make sure it gets printed, otherwise
257 # space is there to make sure it gets printed, otherwise
257 # OS buffering sometimes just suppresses it.
258 # OS buffering sometimes just suppresses it.
258 write(' \n')
259 write(' \n')
259 self.out.flush()
260 self.out.flush()
260 else:
261 else:
261 if interact:
262 if interact:
262 e="Further interaction is not possible: child process is dead."
263 e="Further interaction is not possible: child process is dead."
263 print >> sys.stderr, e
264 print >> sys.stderr, e
264
265
265 # Leave the child ready for more input later on, otherwise select just
266 # Leave the child ready for more input later on, otherwise select just
266 # hangs on the second invocation.
267 # hangs on the second invocation.
267 if c.isalive():
268 if c.isalive():
268 c.send('\n')
269 c.send('\n')
269
270
270 # Return any requested output
271 # Return any requested output
271 if get_output:
272 if get_output:
272 return ''.join(output)
273 return ''.join(output)
273
274
274 def main(self,argv=None):
275 def main(self,argv=None):
275 """Run as a command-line script."""
276 """Run as a command-line script."""
276
277
277 parser = optparse.OptionParser(usage=USAGE % self.__class__.__name__)
278 parser = optparse.OptionParser(usage=USAGE % self.__class__.__name__)
278 newopt = parser.add_option
279 newopt = parser.add_option
279 newopt('-i','--interact',action='store_true',default=False,
280 newopt('-i','--interact',action='store_true',default=False,
280 help='Interact with the program after the script is run.')
281 help='Interact with the program after the script is run.')
281
282
282 opts,args = parser.parse_args(argv)
283 opts,args = parser.parse_args(argv)
283
284
284 if len(args) != 1:
285 if len(args) != 1:
285 print >> sys.stderr,"You must supply exactly one file to run."
286 print >> sys.stderr,"You must supply exactly one file to run."
286 sys.exit(1)
287 sys.exit(1)
287
288
288 self.run_file(args[0],opts.interact)
289 self.run_file(args[0],opts.interact)
289
290
291 _ipython_cmd = "ipython3" if py3compat.PY3 else "ipython"
290
292
291 # Specific runners for particular programs
293 # Specific runners for particular programs
292 class IPythonRunner(InteractiveRunner):
294 class IPythonRunner(InteractiveRunner):
293 """Interactive IPython runner.
295 """Interactive IPython runner.
294
296
295 This initalizes IPython in 'nocolor' mode for simplicity. This lets us
297 This initalizes IPython in 'nocolor' mode for simplicity. This lets us
296 avoid having to write a regexp that matches ANSI sequences, though pexpect
298 avoid having to write a regexp that matches ANSI sequences, though pexpect
297 does support them. If anyone contributes patches for ANSI color support,
299 does support them. If anyone contributes patches for ANSI color support,
298 they will be welcome.
300 they will be welcome.
299
301
300 It also sets the prompts manually, since the prompt regexps for
302 It also sets the prompts manually, since the prompt regexps for
301 pexpect need to be matched to the actual prompts, so user-customized
303 pexpect need to be matched to the actual prompts, so user-customized
302 prompts would break this.
304 prompts would break this.
303 """
305 """
304
306
305 def __init__(self,program = 'ipython',args=None,out=sys.stdout,echo=True):
307 def __init__(self,program = _ipython_cmd, args=None, out=sys.stdout, echo=True):
306 """New runner, optionally passing the ipython command to use."""
308 """New runner, optionally passing the ipython command to use."""
307 args0 = ['--colors=NoColor',
309 args0 = ['--colors=NoColor',
308 '--no-term-title',
310 '--no-term-title',
309 '--no-autoindent',
311 '--no-autoindent',
310 # '--quick' is important, to prevent loading default config:
312 # '--quick' is important, to prevent loading default config:
311 '--quick']
313 '--quick']
312 if args is None: args = args0
314 if args is None: args = args0
313 else: args = args0 + args
315 else: args = args0 + args
314 prompts = [r'In \[\d+\]: ',r' \.*: ']
316 prompts = [r'In \[\d+\]: ',r' \.*: ']
315 InteractiveRunner.__init__(self,program,prompts,args,out,echo)
317 InteractiveRunner.__init__(self,program,prompts,args,out,echo)
316
318
317
319
318 class PythonRunner(InteractiveRunner):
320 class PythonRunner(InteractiveRunner):
319 """Interactive Python runner."""
321 """Interactive Python runner."""
320
322
321 def __init__(self,program='python',args=None,out=sys.stdout,echo=True):
323 def __init__(self,program=sys.executable, args=None, out=sys.stdout, echo=True):
322 """New runner, optionally passing the python command to use."""
324 """New runner, optionally passing the python command to use."""
323
325
324 prompts = [r'>>> ',r'\.\.\. ']
326 prompts = [r'>>> ',r'\.\.\. ']
325 InteractiveRunner.__init__(self,program,prompts,args,out,echo)
327 InteractiveRunner.__init__(self,program,prompts,args,out,echo)
326
328
327
329
328 class SAGERunner(InteractiveRunner):
330 class SAGERunner(InteractiveRunner):
329 """Interactive SAGE runner.
331 """Interactive SAGE runner.
330
332
331 WARNING: this runner only works if you manually adjust your SAGE
333 WARNING: this runner only works if you manually adjust your SAGE
332 configuration so that the 'color' option in the configuration file is set to
334 configuration so that the 'color' option in the configuration file is set to
333 'NoColor', because currently the prompt matching regexp does not identify
335 'NoColor', because currently the prompt matching regexp does not identify
334 color sequences."""
336 color sequences."""
335
337
336 def __init__(self,program='sage',args=None,out=sys.stdout,echo=True):
338 def __init__(self,program='sage',args=None,out=sys.stdout,echo=True):
337 """New runner, optionally passing the sage command to use."""
339 """New runner, optionally passing the sage command to use."""
338
340
339 prompts = ['sage: ',r'\s*\.\.\. ']
341 prompts = ['sage: ',r'\s*\.\.\. ']
340 InteractiveRunner.__init__(self,program,prompts,args,out,echo)
342 InteractiveRunner.__init__(self,program,prompts,args,out,echo)
341
343
342
344
343 class RunnerFactory(object):
345 class RunnerFactory(object):
344 """Code runner factory.
346 """Code runner factory.
345
347
346 This class provides an IPython code runner, but enforces that only one
348 This class provides an IPython code runner, but enforces that only one
347 runner is ever instantiated. The runner is created based on the extension
349 runner is ever instantiated. The runner is created based on the extension
348 of the first file to run, and it raises an exception if a runner is later
350 of the first file to run, and it raises an exception if a runner is later
349 requested for a different extension type.
351 requested for a different extension type.
350
352
351 This ensures that we don't generate example files for doctest with a mix of
353 This ensures that we don't generate example files for doctest with a mix of
352 python and ipython syntax.
354 python and ipython syntax.
353 """
355 """
354
356
355 def __init__(self,out=sys.stdout):
357 def __init__(self,out=sys.stdout):
356 """Instantiate a code runner."""
358 """Instantiate a code runner."""
357
359
358 self.out = out
360 self.out = out
359 self.runner = None
361 self.runner = None
360 self.runnerClass = None
362 self.runnerClass = None
361
363
362 def _makeRunner(self,runnerClass):
364 def _makeRunner(self,runnerClass):
363 self.runnerClass = runnerClass
365 self.runnerClass = runnerClass
364 self.runner = runnerClass(out=self.out)
366 self.runner = runnerClass(out=self.out)
365 return self.runner
367 return self.runner
366
368
367 def __call__(self,fname):
369 def __call__(self,fname):
368 """Return a runner for the given filename."""
370 """Return a runner for the given filename."""
369
371
370 if fname.endswith('.py'):
372 if fname.endswith('.py'):
371 runnerClass = PythonRunner
373 runnerClass = PythonRunner
372 elif fname.endswith('.ipy'):
374 elif fname.endswith('.ipy'):
373 runnerClass = IPythonRunner
375 runnerClass = IPythonRunner
374 else:
376 else:
375 raise ValueError('Unknown file type for Runner: %r' % fname)
377 raise ValueError('Unknown file type for Runner: %r' % fname)
376
378
377 if self.runner is None:
379 if self.runner is None:
378 return self._makeRunner(runnerClass)
380 return self._makeRunner(runnerClass)
379 else:
381 else:
380 if runnerClass==self.runnerClass:
382 if runnerClass==self.runnerClass:
381 return self.runner
383 return self.runner
382 else:
384 else:
383 e='A runner of type %r can not run file %r' % \
385 e='A runner of type %r can not run file %r' % \
384 (self.runnerClass,fname)
386 (self.runnerClass,fname)
385 raise ValueError(e)
387 raise ValueError(e)
386
388
387
389
388 # Global usage string, to avoid indentation issues if typed in a function def.
390 # Global usage string, to avoid indentation issues if typed in a function def.
389 MAIN_USAGE = """
391 MAIN_USAGE = """
390 %prog [options] file_to_run
392 %prog [options] file_to_run
391
393
392 This is an interface to the various interactive runners available in this
394 This is an interface to the various interactive runners available in this
393 module. If you want to pass specific options to one of the runners, you need
395 module. If you want to pass specific options to one of the runners, you need
394 to first terminate the main options with a '--', and then provide the runner's
396 to first terminate the main options with a '--', and then provide the runner's
395 options. For example:
397 options. For example:
396
398
397 irunner.py --python -- --help
399 irunner.py --python -- --help
398
400
399 will pass --help to the python runner. Similarly,
401 will pass --help to the python runner. Similarly,
400
402
401 irunner.py --ipython -- --interact script.ipy
403 irunner.py --ipython -- --interact script.ipy
402
404
403 will run the script.ipy file under the IPython runner, and then will start to
405 will run the script.ipy file under the IPython runner, and then will start to
404 interact with IPython at the end of the script (instead of exiting).
406 interact with IPython at the end of the script (instead of exiting).
405
407
406 The already implemented runners are listed below; adding one for a new program
408 The already implemented runners are listed below; adding one for a new program
407 is a trivial task, see the source for examples.
409 is a trivial task, see the source for examples.
408 """
410 """
409
411
410 def main():
412 def main():
411 """Run as a command-line script."""
413 """Run as a command-line script."""
412
414
413 parser = optparse.OptionParser(usage=MAIN_USAGE)
415 parser = optparse.OptionParser(usage=MAIN_USAGE)
414 newopt = parser.add_option
416 newopt = parser.add_option
415 newopt('--ipython',action='store_const',dest='mode',const='ipython',
417 newopt('--ipython',action='store_const',dest='mode',const='ipython',
416 help='IPython interactive runner (default).')
418 help='IPython interactive runner (default).')
417 newopt('--python',action='store_const',dest='mode',const='python',
419 newopt('--python',action='store_const',dest='mode',const='python',
418 help='Python interactive runner.')
420 help='Python interactive runner.')
419 newopt('--sage',action='store_const',dest='mode',const='sage',
421 newopt('--sage',action='store_const',dest='mode',const='sage',
420 help='SAGE interactive runner.')
422 help='SAGE interactive runner.')
421
423
422 opts,args = parser.parse_args()
424 opts,args = parser.parse_args()
423 runners = dict(ipython=IPythonRunner,
425 runners = dict(ipython=IPythonRunner,
424 python=PythonRunner,
426 python=PythonRunner,
425 sage=SAGERunner)
427 sage=SAGERunner)
426
428
427 try:
429 try:
428 ext = os.path.splitext(args[0])[-1]
430 ext = os.path.splitext(args[0])[-1]
429 except IndexError:
431 except IndexError:
430 ext = ''
432 ext = ''
431 modes = {'.ipy':'ipython',
433 modes = {'.ipy':'ipython',
432 '.py':'python',
434 '.py':'python',
433 '.sage':'sage'}
435 '.sage':'sage'}
434 mode = modes.get(ext,"ipython")
436 mode = modes.get(ext,"ipython")
435 if opts.mode:
437 if opts.mode:
436 mode = opts.mode
438 mode = opts.mode
437 runners[mode]().main(args)
439 runners[mode]().main(args)
438
440
439 if __name__ == '__main__':
441 if __name__ == '__main__':
440 main()
442 main()
@@ -1,174 +1,182
1 """Test suite for the irunner module.
1 """Test suite for the irunner module.
2
2
3 Not the most elegant or fine-grained, but it does cover at least the bulk
3 Not the most elegant or fine-grained, but it does cover at least the bulk
4 functionality."""
4 functionality."""
5
5
6 # Global to make tests extra verbose and help debugging
6 # Global to make tests extra verbose and help debugging
7 VERBOSE = True
7 VERBOSE = True
8
8
9 # stdlib imports
9 # stdlib imports
10 import StringIO
10 import StringIO
11 import sys
11 import sys
12 import unittest
12 import unittest
13
13
14 # IPython imports
14 # IPython imports
15 from IPython.lib import irunner
15 from IPython.lib import irunner
16 from IPython.testing.decorators import known_failure_py3
16 from IPython.testing.decorators import known_failure_py3
17 from IPython.utils.py3compat import doctest_refactor_print
17
18
18 # Testing code begins
19 # Testing code begins
19 class RunnerTestCase(unittest.TestCase):
20 class RunnerTestCase(unittest.TestCase):
20
21
21 def setUp(self):
22 def setUp(self):
22 self.out = StringIO.StringIO()
23 self.out = StringIO.StringIO()
23 #self.out = sys.stdout
24 #self.out = sys.stdout
24
25
25 def _test_runner(self,runner,source,output):
26 def _test_runner(self,runner,source,output):
26 """Test that a given runner's input/output match."""
27 """Test that a given runner's input/output match."""
27
28
28 runner.run_source(source)
29 runner.run_source(source)
29 out = self.out.getvalue()
30 out = self.out.getvalue()
30 #out = ''
31 #out = ''
31 # this output contains nasty \r\n lineends, and the initial ipython
32 # this output contains nasty \r\n lineends, and the initial ipython
32 # banner. clean it up for comparison, removing lines of whitespace
33 # banner. clean it up for comparison, removing lines of whitespace
33 output_l = [l for l in output.splitlines() if l and not l.isspace()]
34 output_l = [l for l in output.splitlines() if l and not l.isspace()]
34 out_l = [l for l in out.splitlines() if l and not l.isspace()]
35 out_l = [l for l in out.splitlines() if l and not l.isspace()]
35 mismatch = 0
36 mismatch = 0
36 if len(output_l) != len(out_l):
37 if len(output_l) != len(out_l):
37 message = ("Mismatch in number of lines\n\n"
38 message = ("Mismatch in number of lines\n\n"
38 "Expected:\n"
39 "Expected:\n"
39 "~~~~~~~~~\n"
40 "~~~~~~~~~\n"
40 "%s\n\n"
41 "%s\n\n"
41 "Got:\n"
42 "Got:\n"
42 "~~~~~~~~~\n"
43 "~~~~~~~~~\n"
43 "%s"
44 "%s"
44 ) % ("\n".join(output_l), "\n".join(out_l))
45 ) % ("\n".join(output_l), "\n".join(out_l))
45 self.fail(message)
46 self.fail(message)
46 for n in range(len(output_l)):
47 for n in range(len(output_l)):
47 # Do a line-by-line comparison
48 # Do a line-by-line comparison
48 ol1 = output_l[n].strip()
49 ol1 = output_l[n].strip()
49 ol2 = out_l[n].strip()
50 ol2 = out_l[n].strip()
50 if ol1 != ol2:
51 if ol1 != ol2:
51 mismatch += 1
52 mismatch += 1
52 if VERBOSE:
53 if VERBOSE:
53 print '<<< line %s does not match:' % n
54 print '<<< line %s does not match:' % n
54 print repr(ol1)
55 print repr(ol1)
55 print repr(ol2)
56 print repr(ol2)
56 print '>>>'
57 print '>>>'
57 self.assert_(mismatch==0,'Number of mismatched lines: %s' %
58 self.assert_(mismatch==0,'Number of mismatched lines: %s' %
58 mismatch)
59 mismatch)
59
60
60 # irunner isn't working on Python 3 (due to pexpect)
61 # The SyntaxError appears differently in Python 3, for some reason.
61 @known_failure_py3
62 @known_failure_py3
62 def testIPython(self):
63 def testIPython(self):
63 """Test the IPython runner."""
64 """Test the IPython runner."""
64 source = """
65 source = doctest_refactor_print("""
65 print 'hello, this is python'
66 print 'hello, this is python'
66 # some more code
67 # some more code
67 x=1;y=2
68 x=1;y=2
68 x+y**2
69 x+y**2
69
70
70 # An example of autocall functionality
71 # An example of autocall functionality
71 from math import *
72 from math import *
72 autocall 1
73 autocall 1
73 cos pi
74 cos pi
74 autocall 0
75 autocall 0
75 cos pi
76 cos pi
76 cos(pi)
77 cos(pi)
77
78
78 for i in range(5):
79 for i in range(5):
79 print i,
80 print i
80
81
81 print "that's all folks!"
82 print "that's all folks!"
82
83
83 exit
84 exit
84 """
85 """)
85 output = """\
86 output = doctest_refactor_print("""\
86 In [1]: print 'hello, this is python'
87 In [1]: print 'hello, this is python'
87 hello, this is python
88 hello, this is python
88
89
89
90
90 # some more code
91 # some more code
91 In [2]: x=1;y=2
92 In [2]: x=1;y=2
92
93
93 In [3]: x+y**2
94 In [3]: x+y**2
94 Out[3]: 5
95 Out[3]: 5
95
96
96
97
97 # An example of autocall functionality
98 # An example of autocall functionality
98 In [4]: from math import *
99 In [4]: from math import *
99
100
100 In [5]: autocall 1
101 In [5]: autocall 1
101 Automatic calling is: Smart
102 Automatic calling is: Smart
102
103
103 In [6]: cos pi
104 In [6]: cos pi
104 ------> cos(pi)
105 ------> cos(pi)
105 Out[6]: -1.0
106 Out[6]: -1.0
106
107
107 In [7]: autocall 0
108 In [7]: autocall 0
108 Automatic calling is: OFF
109 Automatic calling is: OFF
109
110
110 In [8]: cos pi
111 In [8]: cos pi
111 File "<ipython-input-8-6bd7313dd9a9>", line 1
112 File "<ipython-input-8-6bd7313dd9a9>", line 1
112 cos pi
113 cos pi
113 ^
114 ^
114 SyntaxError: invalid syntax
115 SyntaxError: invalid syntax
115
116
116
117
117 In [9]: cos(pi)
118 In [9]: cos(pi)
118 Out[9]: -1.0
119 Out[9]: -1.0
119
120
120
121
121 In [10]: for i in range(5):
122 In [10]: for i in range(5):
122 ....: print i,
123 ....: print i
123 ....:
124 ....:
124 0 1 2 3 4
125 0
126 1
127 2
128 3
129 4
125
130
126 In [11]: print "that's all folks!"
131 In [11]: print "that's all folks!"
127 that's all folks!
132 that's all folks!
128
133
129
134
130 In [12]: exit
135 In [12]: exit
131 """
136 """)
132 runner = irunner.IPythonRunner(out=self.out)
137 runner = irunner.IPythonRunner(out=self.out)
133 self._test_runner(runner,source,output)
138 self._test_runner(runner,source,output)
134
139
135 @known_failure_py3
136 def testPython(self):
140 def testPython(self):
137 """Test the Python runner."""
141 """Test the Python runner."""
138 runner = irunner.PythonRunner(out=self.out)
142 runner = irunner.PythonRunner(out=self.out)
139 source = """
143 source = doctest_refactor_print("""
140 print 'hello, this is python'
144 print 'hello, this is python'
141
145
142 # some more code
146 # some more code
143 x=1;y=2
147 x=1;y=2
144 x+y**2
148 x+y**2
145
149
146 from math import *
150 from math import *
147 cos(pi)
151 cos(pi)
148
152
149 for i in range(5):
153 for i in range(5):
150 print i,
154 print i
151
155
152 print "that's all folks!"
156 print "that's all folks!"
153 """
157 """)
154 output = """\
158 output = doctest_refactor_print("""\
155 >>> print 'hello, this is python'
159 >>> print 'hello, this is python'
156 hello, this is python
160 hello, this is python
157
161
158 # some more code
162 # some more code
159 >>> x=1;y=2
163 >>> x=1;y=2
160 >>> x+y**2
164 >>> x+y**2
161 5
165 5
162
166
163 >>> from math import *
167 >>> from math import *
164 >>> cos(pi)
168 >>> cos(pi)
165 -1.0
169 -1.0
166
170
167 >>> for i in range(5):
171 >>> for i in range(5):
168 ... print i,
172 ... print i
169 ...
173 ...
170 0 1 2 3 4
174 0
175 1
176 2
177 3
178 4
171 >>> print "that's all folks!"
179 >>> print "that's all folks!"
172 that's all folks!
180 that's all folks!
173 """
181 """)
174 self._test_runner(runner,source,output)
182 self._test_runner(runner,source,output)
@@ -1,110 +1,108
1 """Test suite for pylab_import_all magic
1 """Test suite for pylab_import_all magic
2 Modified from the irunner module but using regex.
2 Modified from the irunner module but using regex.
3 """
3 """
4
4
5 # Global to make tests extra verbose and help debugging
5 # Global to make tests extra verbose and help debugging
6 VERBOSE = True
6 VERBOSE = True
7
7
8 # stdlib imports
8 # stdlib imports
9 import StringIO
9 import StringIO
10 import sys
10 import sys
11 import unittest
11 import unittest
12 import re
12 import re
13
13
14 # IPython imports
14 # IPython imports
15 from IPython.lib import irunner
15 from IPython.lib import irunner
16 from IPython.testing import decorators
16 from IPython.testing import decorators
17
17
18 # Testing code begins
18 # Testing code begins
19 class RunnerTestCase(unittest.TestCase):
19 class RunnerTestCase(unittest.TestCase):
20
20
21 def setUp(self):
21 def setUp(self):
22 self.out = StringIO.StringIO()
22 self.out = StringIO.StringIO()
23 #self.out = sys.stdout
23 #self.out = sys.stdout
24
24
25 @decorators.known_failure_py3
26 def _test_runner(self,runner,source,output):
25 def _test_runner(self,runner,source,output):
27 """Test that a given runner's input/output match."""
26 """Test that a given runner's input/output match."""
28
27
29 runner.run_source(source)
28 runner.run_source(source)
30 out = self.out.getvalue()
29 out = self.out.getvalue()
31 #out = ''
30 #out = ''
32 # this output contains nasty \r\n lineends, and the initial ipython
31 # this output contains nasty \r\n lineends, and the initial ipython
33 # banner. clean it up for comparison, removing lines of whitespace
32 # banner. clean it up for comparison, removing lines of whitespace
34 output_l = [l for l in output.splitlines() if l and not l.isspace()]
33 output_l = [l for l in output.splitlines() if l and not l.isspace()]
35 out_l = [l for l in out.splitlines() if l and not l.isspace()]
34 out_l = [l for l in out.splitlines() if l and not l.isspace()]
36 mismatch = 0
35 mismatch = 0
37 if len(output_l) != len(out_l):
36 if len(output_l) != len(out_l):
38 message = ("Mismatch in number of lines\n\n"
37 message = ("Mismatch in number of lines\n\n"
39 "Expected:\n"
38 "Expected:\n"
40 "~~~~~~~~~\n"
39 "~~~~~~~~~\n"
41 "%s\n\n"
40 "%s\n\n"
42 "Got:\n"
41 "Got:\n"
43 "~~~~~~~~~\n"
42 "~~~~~~~~~\n"
44 "%s"
43 "%s"
45 ) % ("\n".join(output_l), "\n".join(out_l))
44 ) % ("\n".join(output_l), "\n".join(out_l))
46 self.fail(message)
45 self.fail(message)
47 for n in range(len(output_l)):
46 for n in range(len(output_l)):
48 # Do a line-by-line comparison
47 # Do a line-by-line comparison
49 ol1 = output_l[n].strip()
48 ol1 = output_l[n].strip()
50 ol2 = out_l[n].strip()
49 ol2 = out_l[n].strip()
51 if not re.match(ol1,ol2):
50 if not re.match(ol1,ol2):
52 mismatch += 1
51 mismatch += 1
53 if VERBOSE:
52 if VERBOSE:
54 print '<<< line %s does not match:' % n
53 print '<<< line %s does not match:' % n
55 print repr(ol1)
54 print repr(ol1)
56 print repr(ol2)
55 print repr(ol2)
57 print '>>>'
56 print '>>>'
58 self.assert_(mismatch==0,'Number of mismatched lines: %s' %
57 self.assert_(mismatch==0,'Number of mismatched lines: %s' %
59 mismatch)
58 mismatch)
60
59
61 @decorators.skipif_not_matplotlib
60 @decorators.skipif_not_matplotlib
62 def test_pylab_import_all_enabled(self):
61 def test_pylab_import_all_enabled(self):
63 "Verify that plot is available when pylab_import_all = True"
62 "Verify that plot is available when pylab_import_all = True"
64 source = """
63 source = """
65 from IPython.config.application import Application
64 from IPython.config.application import Application
66 app = Application.instance()
65 app = Application.instance()
67 app.pylab_import_all = True
66 app.pylab_import_all = True
68 pylab
67 pylab
69 ip=get_ipython()
68 ip=get_ipython()
70 'plot' in ip.user_ns
69 'plot' in ip.user_ns
71 """
70 """
72 output = """
71 output = """
73 In \[1\]: from IPython\.config\.application import Application
72 In \[1\]: from IPython\.config\.application import Application
74 In \[2\]: app = Application\.instance\(\)
73 In \[2\]: app = Application\.instance\(\)
75 In \[3\]: app\.pylab_import_all = True
74 In \[3\]: app\.pylab_import_all = True
76 In \[4\]: pylab
75 In \[4\]: pylab
77 ^Welcome to pylab, a matplotlib-based Python environment
76 ^Welcome to pylab, a matplotlib-based Python environment
78 For more information, type 'help\(pylab\)'\.
77 For more information, type 'help\(pylab\)'\.
79 In \[5\]: ip=get_ipython\(\)
78 In \[5\]: ip=get_ipython\(\)
80 In \[6\]: \'plot\' in ip\.user_ns
79 In \[6\]: \'plot\' in ip\.user_ns
81 Out\[6\]: True
80 Out\[6\]: True
82 """
81 """
83 runner = irunner.IPythonRunner(out=self.out)
82 runner = irunner.IPythonRunner(out=self.out)
84 self._test_runner(runner,source,output)
83 self._test_runner(runner,source,output)
85
84
86 @decorators.known_failure_py3
87 @decorators.skipif_not_matplotlib
85 @decorators.skipif_not_matplotlib
88 def test_pylab_import_all_disabled(self):
86 def test_pylab_import_all_disabled(self):
89 "Verify that plot is not available when pylab_import_all = False"
87 "Verify that plot is not available when pylab_import_all = False"
90 source = """
88 source = """
91 from IPython.config.application import Application
89 from IPython.config.application import Application
92 app = Application.instance()
90 app = Application.instance()
93 app.pylab_import_all = False
91 app.pylab_import_all = False
94 pylab
92 pylab
95 ip=get_ipython()
93 ip=get_ipython()
96 'plot' in ip.user_ns
94 'plot' in ip.user_ns
97 """
95 """
98 output = """
96 output = """
99 In \[1\]: from IPython\.config\.application import Application
97 In \[1\]: from IPython\.config\.application import Application
100 In \[2\]: app = Application\.instance\(\)
98 In \[2\]: app = Application\.instance\(\)
101 In \[3\]: app\.pylab_import_all = False
99 In \[3\]: app\.pylab_import_all = False
102 In \[4\]: pylab
100 In \[4\]: pylab
103 ^Welcome to pylab, a matplotlib-based Python environment
101 ^Welcome to pylab, a matplotlib-based Python environment
104 For more information, type 'help\(pylab\)'\.
102 For more information, type 'help\(pylab\)'\.
105 In \[5\]: ip=get_ipython\(\)
103 In \[5\]: ip=get_ipython\(\)
106 In \[6\]: \'plot\' in ip\.user_ns
104 In \[6\]: \'plot\' in ip\.user_ns
107 Out\[6\]: False
105 Out\[6\]: False
108 """
106 """
109 runner = irunner.IPythonRunner(out=self.out)
107 runner = irunner.IPythonRunner(out=self.out)
110 self._test_runner(runner,source,output)
108 self._test_runner(runner,source,output)
General Comments 0
You need to be logged in to leave comments. Login now