##// END OF EJS Templates
- add delay parameter to irunner, and standalone irunner script....
fperez -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,9 b''
1 #!/usr/bin/env python
2
3 """Thin wrapper around the IPython irunner module.
4
5 Run with --help for details, or see the irunner source."""
6
7 from IPython import irunner
8
9 irunner.main()
@@ -1,275 +1,294 b''
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://scipy.net/pipermail/ipython-user/2006-May/001705.html
19 http://scipy.net/pipermail/ipython-user/2006-May/001705.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 sys
35 import sys
36
36
37 # Third-party modules.
37 # Third-party modules.
38 import pexpect
38 import pexpect
39
39
40 # Global usage strings, to avoid indentation issues when typing it below.
40 # Global usage strings, to avoid indentation issues when typing it below.
41 USAGE = """
41 USAGE = """
42 Interactive script runner, type: %s
42 Interactive script runner, type: %s
43
43
44 runner [opts] script_name
44 runner [opts] script_name
45 """
45 """
46
46
47 # The generic runner class
47 # The generic runner class
48 class InteractiveRunner(object):
48 class InteractiveRunner(object):
49 """Class to run a sequence of commands through an interactive program."""
49 """Class to run a sequence of commands through an interactive program."""
50
50
51 def __init__(self,program,prompts,args=None):
51 def __init__(self,program,prompts,args=None):
52 """Construct a runner.
52 """Construct a runner.
53
53
54 Inputs:
54 Inputs:
55
55
56 - program: command to execute the given program.
56 - program: command to execute the given program.
57
57
58 - prompts: a list of patterns to match as valid prompts, in the
58 - prompts: a list of patterns to match as valid prompts, in the
59 format used by pexpect. This basically means that it can be either
59 format used by pexpect. This basically means that it can be either
60 a string (to be compiled as a regular expression) or a list of such
60 a string (to be compiled as a regular expression) or a list of such
61 (it must be a true list, as pexpect does type checks).
61 (it must be a true list, as pexpect does type checks).
62
62
63 If more than one prompt is given, the first is treated as the main
63 If more than one prompt is given, the first is treated as the main
64 program prompt and the others as 'continuation' prompts, like
64 program prompt and the others as 'continuation' prompts, like
65 python's. This means that blank lines in the input source are
65 python's. This means that blank lines in the input source are
66 ommitted when the first prompt is matched, but are NOT ommitted when
66 ommitted when the first prompt is matched, but are NOT ommitted when
67 the continuation one matches, since this is how python signals the
67 the continuation one matches, since this is how python signals the
68 end of multiline input interactively.
68 end of multiline input interactively.
69
69
70 Optional inputs:
70 Optional inputs:
71
71
72 - args(None): optional list of strings to pass as arguments to the
72 - args(None): optional list of strings to pass as arguments to the
73 child program.
73 child program.
74
75 Public members not parameterized in the constructor:
76
77 - delaybeforesend(0): Newer versions of pexpect have a delay before
78 sending each new input. For our purposes here, it's typically best
79 to just set this to zero, but if you encounter reliability problems
80 or want an interactive run to pause briefly at each prompt, just
81 increase this value (it is measured in seconds). Note that this
82 variable is not honored at all by older versions of pexpect.
74 """
83 """
75
84
76 self.program = program
85 self.program = program
77 self.prompts = prompts
86 self.prompts = prompts
78 if args is None: args = []
87 if args is None: args = []
79 self.args = args
88 self.args = args
89 # Other public members which we don't make as parameters, but which
90 # users may occasionally want to tweak
91 self.delaybeforesend = 0
80
92
81 def run_file(self,fname,interact=False):
93 def run_file(self,fname,interact=False):
82 """Run the given file interactively.
94 """Run the given file interactively.
83
95
84 Inputs:
96 Inputs:
85
97
86 -fname: name of the file to execute.
98 -fname: name of the file to execute.
87
99
88 See the run_source docstring for the meaning of the optional
100 See the run_source docstring for the meaning of the optional
89 arguments."""
101 arguments."""
90
102
91 fobj = open(fname,'r')
103 fobj = open(fname,'r')
92 try:
104 try:
93 self.run_source(fobj,interact)
105 self.run_source(fobj,interact)
94 finally:
106 finally:
95 fobj.close()
107 fobj.close()
96
108
97 def run_source(self,source,interact=False):
109 def run_source(self,source,interact=False):
98 """Run the given source code interactively.
110 """Run the given source code interactively.
99
111
100 Inputs:
112 Inputs:
101
113
102 - source: a string of code to be executed, or an open file object we
114 - source: a string of code to be executed, or an open file object we
103 can iterate over.
115 can iterate over.
104
116
105 Optional inputs:
117 Optional inputs:
106
118
107 - interact(False): if true, start to interact with the running
119 - interact(False): if true, start to interact with the running
108 program at the end of the script. Otherwise, just exit.
120 program at the end of the script. Otherwise, just exit.
109 """
121 """
110
122
111 # if the source is a string, chop it up in lines so we can iterate
123 # if the source is a string, chop it up in lines so we can iterate
112 # over it just as if it were an open file.
124 # over it just as if it were an open file.
113 if not isinstance(source,file):
125 if not isinstance(source,file):
114 source = source.splitlines(True)
126 source = source.splitlines(True)
115
127
116 # grab the true write method of stdout, in case anything later
128 # grab the true write method of stdout, in case anything later
117 # reassigns sys.stdout, so that we really are writing to the true
129 # reassigns sys.stdout, so that we really are writing to the true
118 # stdout and not to something else.
130 # stdout and not to something else.
119 write = sys.stdout.write
131 write = sys.stdout.write
120
132
121 c = pexpect.spawn(self.program,self.args,timeout=None)
133 c = pexpect.spawn(self.program,self.args,timeout=None)
134 c.delaybeforesend = self.delaybeforesend
122
135
123 prompts = c.compile_pattern_list(self.prompts)
136 prompts = c.compile_pattern_list(self.prompts)
124
137
125 prompt_idx = c.expect_list(prompts)
138 prompt_idx = c.expect_list(prompts)
126 # Flag whether the script ends normally or not, to know whether we can
139 # Flag whether the script ends normally or not, to know whether we can
127 # do anything further with the underlying process.
140 # do anything further with the underlying process.
128 end_normal = True
141 end_normal = True
129 for cmd in source:
142 for cmd in source:
130 # skip blank lines for all matches to the 'main' prompt, while the
143 # skip blank lines for all matches to the 'main' prompt, while the
131 # secondary prompts do not
144 # secondary prompts do not
132 if prompt_idx==0 and cmd.isspace():
145 if prompt_idx==0 and cmd.isspace():
133 continue
146 continue
134
147
135 write(c.after)
148 write(c.after)
136 c.send(cmd)
149 c.send(cmd)
137 try:
150 try:
138 prompt_idx = c.expect_list(prompts)
151 prompt_idx = c.expect_list(prompts)
139 except pexpect.EOF:
152 except pexpect.EOF:
140 # this will happen if the child dies unexpectedly
153 # this will happen if the child dies unexpectedly
141 write(c.before)
154 write(c.before)
142 end_normal = False
155 end_normal = False
143 break
156 break
144 write(c.before)
157 write(c.before)
145
158
146 if isinstance(source,file):
159 if isinstance(source,file):
147 source.close()
160 source.close()
148
161
149 if end_normal:
162 if end_normal:
150 if interact:
163 if interact:
151 c.send('\n')
164 c.send('\n')
152 print '<< Starting interactive mode >>',
165 print '<< Starting interactive mode >>',
153 try:
166 try:
154 c.interact()
167 c.interact()
155 except OSError:
168 except OSError:
156 # This is what fires when the child stops. Simply print a
169 # This is what fires when the child stops. Simply print a
157 # newline so the system prompt is aligned. The extra
170 # newline so the system prompt is aligned. The extra
158 # space is there to make sure it gets printed, otherwise
171 # space is there to make sure it gets printed, otherwise
159 # OS buffering sometimes just suppresses it.
172 # OS buffering sometimes just suppresses it.
160 write(' \n')
173 write(' \n')
161 sys.stdout.flush()
174 sys.stdout.flush()
162 else:
175 else:
163 c.close()
176 c.close()
164 else:
177 else:
165 if interact:
178 if interact:
166 e="Further interaction is not possible: child process is dead."
179 e="Further interaction is not possible: child process is dead."
167 print >> sys.stderr, e
180 print >> sys.stderr, e
168
181
169 def main(self,argv=None):
182 def main(self,argv=None):
170 """Run as a command-line script."""
183 """Run as a command-line script."""
171
184
172 parser = optparse.OptionParser(usage=USAGE % self.__class__.__name__)
185 parser = optparse.OptionParser(usage=USAGE % self.__class__.__name__)
173 newopt = parser.add_option
186 newopt = parser.add_option
174 newopt('-i','--interact',action='store_true',default=False,
187 newopt('-i','--interact',action='store_true',default=False,
175 help='Interact with the program after the script is run.')
188 help='Interact with the program after the script is run.')
176
189
177 opts,args = parser.parse_args(argv)
190 opts,args = parser.parse_args(argv)
178
191
179 if len(args) != 1:
192 if len(args) != 1:
180 print >> sys.stderr,"You must supply exactly one file to run."
193 print >> sys.stderr,"You must supply exactly one file to run."
181 sys.exit(1)
194 sys.exit(1)
182
195
183 self.run_file(args[0],opts.interact)
196 self.run_file(args[0],opts.interact)
184
197
185
198
186 # Specific runners for particular programs
199 # Specific runners for particular programs
187 class IPythonRunner(InteractiveRunner):
200 class IPythonRunner(InteractiveRunner):
188 """Interactive IPython runner.
201 """Interactive IPython runner.
189
202
190 This initalizes IPython in 'nocolor' mode for simplicity. This lets us
203 This initalizes IPython in 'nocolor' mode for simplicity. This lets us
191 avoid having to write a regexp that matches ANSI sequences, though pexpect
204 avoid having to write a regexp that matches ANSI sequences, though pexpect
192 does support them. If anyone contributes patches for ANSI color support,
205 does support them. If anyone contributes patches for ANSI color support,
193 they will be welcome.
206 they will be welcome.
194
207
195 It also sets the prompts manually, since the prompt regexps for
208 It also sets the prompts manually, since the prompt regexps for
196 pexpect need to be matched to the actual prompts, so user-customized
209 pexpect need to be matched to the actual prompts, so user-customized
197 prompts would break this.
210 prompts would break this.
198 """
211 """
199
212
200 def __init__(self,program = 'ipython',args=None):
213 def __init__(self,program = 'ipython',args=None):
201 """New runner, optionally passing the ipython command to use."""
214 """New runner, optionally passing the ipython command to use."""
202
215
203 args0 = ['-colors','NoColor',
216 args0 = ['-colors','NoColor',
204 '-pi1','In [\\#]: ',
217 '-pi1','In [\\#]: ',
205 '-pi2',' .\\D.: ']
218 '-pi2',' .\\D.: ']
206 if args is None: args = args0
219 if args is None: args = args0
207 else: args = args0 + args
220 else: args = args0 + args
208 prompts = [r'In \[\d+\]: ',r' \.*: ']
221 prompts = [r'In \[\d+\]: ',r' \.*: ']
209 InteractiveRunner.__init__(self,program,prompts,args)
222 InteractiveRunner.__init__(self,program,prompts,args)
210
223
211
224
212 class PythonRunner(InteractiveRunner):
225 class PythonRunner(InteractiveRunner):
213 """Interactive Python runner."""
226 """Interactive Python runner."""
214
227
215 def __init__(self,program='python',args=None):
228 def __init__(self,program='python',args=None):
216 """New runner, optionally passing the python command to use."""
229 """New runner, optionally passing the python command to use."""
217
230
218 prompts = [r'>>> ',r'\.\.\. ']
231 prompts = [r'>>> ',r'\.\.\. ']
219 InteractiveRunner.__init__(self,program,prompts,args)
232 InteractiveRunner.__init__(self,program,prompts,args)
220
233
221
234
222 class SAGERunner(InteractiveRunner):
235 class SAGERunner(InteractiveRunner):
223 """Interactive SAGE runner.
236 """Interactive SAGE runner.
224
237
225 XXX - This class is currently untested, meant for feedback from the SAGE
238 WARNING: this runner only works if you manually configure your SAGE copy
226 team. """
239 to use 'colors NoColor' in the ipythonrc config file, since currently the
240 prompt matching regexp does not identify color sequences."""
227
241
228 def __init__(self,program='sage',args=None):
242 def __init__(self,program='sage',args=None):
229 """New runner, optionally passing the sage command to use."""
243 """New runner, optionally passing the sage command to use."""
230 print 'XXX - This class is currently untested!!!'
231 print 'It is a placeholder, meant for feedback from the SAGE team.'
232
244
233 prompts = ['sage: ',r'\s*\.\.\. ']
245 prompts = ['sage: ',r'\s*\.\.\. ']
234 InteractiveRunner.__init__(self,program,prompts,args)
246 InteractiveRunner.__init__(self,program,prompts,args)
235
247
236 # Global usage string, to avoid indentation issues if typed in a function def.
248 # Global usage string, to avoid indentation issues if typed in a function def.
237 MAIN_USAGE = """
249 MAIN_USAGE = """
238 %prog [options] file_to_run
250 %prog [options] file_to_run
239
251
240 This is an interface to the various interactive runners available in this
252 This is an interface to the various interactive runners available in this
241 module. If you want to pass specific options to one of the runners, you need
253 module. If you want to pass specific options to one of the runners, you need
242 to first terminate the main options with a '--', and then provide the runner's
254 to first terminate the main options with a '--', and then provide the runner's
243 options. For example:
255 options. For example:
244
256
245 irunner.py --python -- --help
257 irunner.py --python -- --help
246
258
247 will pass --help to the python runner. Similarly,
259 will pass --help to the python runner. Similarly,
248
260
249 irunner.py --ipython -- --log test.log script.ipy
261 irunner.py --ipython -- --interact script.ipy
262
263 will run the script.ipy file under the IPython runner, and then will start to
264 interact with IPython at the end of the script (instead of exiting).
265
266 The already implemented runners are listed below; adding one for a new program
267 is a trivial task, see the source for examples.
250
268
251 will run the script.ipy file under the IPython runner, logging all output into
269 WARNING: the SAGE runner only works if you manually configure your SAGE copy
252 the test.log file.
270 to use 'colors NoColor' in the ipythonrc config file, since currently the
271 prompt matching regexp does not identify color sequences.
253 """
272 """
254
273
255 def main():
274 def main():
256 """Run as a command-line script."""
275 """Run as a command-line script."""
257
276
258 parser = optparse.OptionParser(usage=MAIN_USAGE)
277 parser = optparse.OptionParser(usage=MAIN_USAGE)
259 newopt = parser.add_option
278 newopt = parser.add_option
260 parser.set_defaults(mode='ipython')
279 parser.set_defaults(mode='ipython')
261 newopt('--ipython',action='store_const',dest='mode',const='ipython',
280 newopt('--ipython',action='store_const',dest='mode',const='ipython',
262 help='IPython interactive runner (default).')
281 help='IPython interactive runner (default).')
263 newopt('--python',action='store_const',dest='mode',const='python',
282 newopt('--python',action='store_const',dest='mode',const='python',
264 help='Python interactive runner.')
283 help='Python interactive runner.')
265 newopt('--sage',action='store_const',dest='mode',const='sage',
284 newopt('--sage',action='store_const',dest='mode',const='sage',
266 help='SAGE interactive runner - UNTESTED.')
285 help='SAGE interactive runner.')
267
286
268 opts,args = parser.parse_args()
287 opts,args = parser.parse_args()
269 runners = dict(ipython=IPythonRunner,
288 runners = dict(ipython=IPythonRunner,
270 python=PythonRunner,
289 python=PythonRunner,
271 sage=SAGERunner)
290 sage=SAGERunner)
272 runners[opts.mode]().main(args)
291 runners[opts.mode]().main(args)
273
292
274 if __name__ == '__main__':
293 if __name__ == '__main__':
275 main()
294 main()
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now