##// END OF EJS Templates
Minor cleanup - leftover debug stuff
fperez -
Show More
@@ -1,276 +1,275 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 """
74 """
75
75
76 self.program = program
76 self.program = program
77 self.prompts = prompts
77 self.prompts = prompts
78 if args is None: args = []
78 if args is None: args = []
79 self.args = args
79 self.args = args
80
80
81 def run_file(self,fname,interact=False):
81 def run_file(self,fname,interact=False):
82 """Run the given file interactively.
82 """Run the given file interactively.
83
83
84 Inputs:
84 Inputs:
85
85
86 -fname: name of the file to execute.
86 -fname: name of the file to execute.
87
87
88 See the run_source docstring for the meaning of the optional
88 See the run_source docstring for the meaning of the optional
89 arguments."""
89 arguments."""
90
90
91 fobj = open(fname,'r')
91 fobj = open(fname,'r')
92 try:
92 try:
93 self.run_source(fobj,interact)
93 self.run_source(fobj,interact)
94 finally:
94 finally:
95 fobj.close()
95 fobj.close()
96
96
97 def run_source(self,source,interact=False):
97 def run_source(self,source,interact=False):
98 """Run the given source code interactively.
98 """Run the given source code interactively.
99
99
100 Inputs:
100 Inputs:
101
101
102 - source: a string of code to be executed, or an open file object we
102 - source: a string of code to be executed, or an open file object we
103 can iterate over.
103 can iterate over.
104
104
105 Optional inputs:
105 Optional inputs:
106
106
107 - interact(False): if true, start to interact with the running
107 - interact(False): if true, start to interact with the running
108 program at the end of the script. Otherwise, just exit.
108 program at the end of the script. Otherwise, just exit.
109 """
109 """
110
110
111 # if the source is a string, chop it up in lines so we can iterate
111 # 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.
112 # over it just as if it were an open file.
113 if not isinstance(source,file):
113 if not isinstance(source,file):
114 source = source.splitlines(True)
114 source = source.splitlines(True)
115
115
116 # grab the true write method of stdout, in case anything later
116 # grab the true write method of stdout, in case anything later
117 # reassigns sys.stdout, so that we really are writing to the true
117 # reassigns sys.stdout, so that we really are writing to the true
118 # stdout and not to something else.
118 # stdout and not to something else.
119 write = sys.stdout.write
119 write = sys.stdout.write
120
120
121 c = pexpect.spawn(self.program,self.args,timeout=None)
121 c = pexpect.spawn(self.program,self.args,timeout=None)
122
122
123 prompts = c.compile_pattern_list(self.prompts[0])
124 prompts = c.compile_pattern_list(self.prompts)
123 prompts = c.compile_pattern_list(self.prompts)
125
124
126 prompt_idx = c.expect_list(prompts)
125 prompt_idx = c.expect_list(prompts)
127 # Flag whether the script ends normally or not, to know whether we can
126 # Flag whether the script ends normally or not, to know whether we can
128 # do anything further with the underlying process.
127 # do anything further with the underlying process.
129 end_normal = True
128 end_normal = True
130 for cmd in source:
129 for cmd in source:
131 # skip blank lines for all matches to the 'main' prompt, while the
130 # skip blank lines for all matches to the 'main' prompt, while the
132 # secondary prompts do not
131 # secondary prompts do not
133 if prompt_idx==0 and cmd.isspace():
132 if prompt_idx==0 and cmd.isspace():
134 continue
133 continue
135
134
136 write(c.after)
135 write(c.after)
137 c.send(cmd)
136 c.send(cmd)
138 try:
137 try:
139 prompt_idx = c.expect_list(prompts)
138 prompt_idx = c.expect_list(prompts)
140 except pexpect.EOF:
139 except pexpect.EOF:
141 # this will happen if the child dies unexpectedly
140 # this will happen if the child dies unexpectedly
142 write(c.before)
141 write(c.before)
143 end_normal = False
142 end_normal = False
144 break
143 break
145 write(c.before)
144 write(c.before)
146
145
147 if isinstance(source,file):
146 if isinstance(source,file):
148 source.close()
147 source.close()
149
148
150 if end_normal:
149 if end_normal:
151 if interact:
150 if interact:
152 c.send('\n')
151 c.send('\n')
153 print '<< Starting interactive mode >>',
152 print '<< Starting interactive mode >>',
154 try:
153 try:
155 c.interact()
154 c.interact()
156 except OSError:
155 except OSError:
157 # This is what fires when the child stops. Simply print a
156 # This is what fires when the child stops. Simply print a
158 # newline so the system prompt is alingned. The extra
157 # newline so the system prompt is aligned. The extra
159 # space is there to make sure it gets printed, otherwise
158 # space is there to make sure it gets printed, otherwise
160 # OS buffering sometimes just suppresses it.
159 # OS buffering sometimes just suppresses it.
161 write(' \n')
160 write(' \n')
162 sys.stdout.flush()
161 sys.stdout.flush()
163 else:
162 else:
164 c.close()
163 c.close()
165 else:
164 else:
166 if interact:
165 if interact:
167 e="Further interaction is not possible: child process is dead."
166 e="Further interaction is not possible: child process is dead."
168 print >> sys.stderr, e
167 print >> sys.stderr, e
169
168
170 def main(self,argv=None):
169 def main(self,argv=None):
171 """Run as a command-line script."""
170 """Run as a command-line script."""
172
171
173 parser = optparse.OptionParser(usage=USAGE % self.__class__.__name__)
172 parser = optparse.OptionParser(usage=USAGE % self.__class__.__name__)
174 newopt = parser.add_option
173 newopt = parser.add_option
175 newopt('-i','--interact',action='store_true',default=False,
174 newopt('-i','--interact',action='store_true',default=False,
176 help='Interact with the program after the script is run.')
175 help='Interact with the program after the script is run.')
177
176
178 opts,args = parser.parse_args(argv)
177 opts,args = parser.parse_args(argv)
179
178
180 if len(args) != 1:
179 if len(args) != 1:
181 print >> sys.stderr,"You must supply exactly one file to run."
180 print >> sys.stderr,"You must supply exactly one file to run."
182 sys.exit(1)
181 sys.exit(1)
183
182
184 self.run_file(args[0],opts.interact)
183 self.run_file(args[0],opts.interact)
185
184
186
185
187 # Specific runners for particular programs
186 # Specific runners for particular programs
188 class IPythonRunner(InteractiveRunner):
187 class IPythonRunner(InteractiveRunner):
189 """Interactive IPython runner.
188 """Interactive IPython runner.
190
189
191 This initalizes IPython in 'nocolor' mode for simplicity. This lets us
190 This initalizes IPython in 'nocolor' mode for simplicity. This lets us
192 avoid having to write a regexp that matches ANSI sequences, though pexpect
191 avoid having to write a regexp that matches ANSI sequences, though pexpect
193 does support them. If anyone contributes patches for ANSI color support,
192 does support them. If anyone contributes patches for ANSI color support,
194 they will be welcome.
193 they will be welcome.
195
194
196 It also sets the prompts manually, since the prompt regexps for
195 It also sets the prompts manually, since the prompt regexps for
197 pexpect need to be matched to the actual prompts, so user-customized
196 pexpect need to be matched to the actual prompts, so user-customized
198 prompts would break this.
197 prompts would break this.
199 """
198 """
200
199
201 def __init__(self,program = 'ipython',args=None):
200 def __init__(self,program = 'ipython',args=None):
202 """New runner, optionally passing the ipython command to use."""
201 """New runner, optionally passing the ipython command to use."""
203
202
204 args0 = ['-colors','NoColor',
203 args0 = ['-colors','NoColor',
205 '-pi1','In [\\#]: ',
204 '-pi1','In [\\#]: ',
206 '-pi2',' .\\D.: ']
205 '-pi2',' .\\D.: ']
207 if args is None: args = args0
206 if args is None: args = args0
208 else: args = args0 + args
207 else: args = args0 + args
209 prompts = [r'In \[\d+\]: ',r' \.*: ']
208 prompts = [r'In \[\d+\]: ',r' \.*: ']
210 InteractiveRunner.__init__(self,program,prompts,args)
209 InteractiveRunner.__init__(self,program,prompts,args)
211
210
212
211
213 class PythonRunner(InteractiveRunner):
212 class PythonRunner(InteractiveRunner):
214 """Interactive Python runner."""
213 """Interactive Python runner."""
215
214
216 def __init__(self,program='python',args=None):
215 def __init__(self,program='python',args=None):
217 """New runner, optionally passing the python command to use."""
216 """New runner, optionally passing the python command to use."""
218
217
219 prompts = [r'>>> ',r'\.\.\. ']
218 prompts = [r'>>> ',r'\.\.\. ']
220 InteractiveRunner.__init__(self,program,prompts,args)
219 InteractiveRunner.__init__(self,program,prompts,args)
221
220
222
221
223 class SAGERunner(InteractiveRunner):
222 class SAGERunner(InteractiveRunner):
224 """Interactive SAGE runner.
223 """Interactive SAGE runner.
225
224
226 XXX - This class is currently untested, meant for feedback from the SAGE
225 XXX - This class is currently untested, meant for feedback from the SAGE
227 team. """
226 team. """
228
227
229 def __init__(self,program='sage',args=None):
228 def __init__(self,program='sage',args=None):
230 """New runner, optionally passing the sage command to use."""
229 """New runner, optionally passing the sage command to use."""
231 print 'XXX - This class is currently untested!!!'
230 print 'XXX - This class is currently untested!!!'
232 print 'It is a placeholder, meant for feedback from the SAGE team.'
231 print 'It is a placeholder, meant for feedback from the SAGE team.'
233
232
234 prompts = ['sage: ',r'\s*\.\.\. ']
233 prompts = ['sage: ',r'\s*\.\.\. ']
235 InteractiveRunner.__init__(self,program,prompts,args)
234 InteractiveRunner.__init__(self,program,prompts,args)
236
235
237 # Global usage string, to avoid indentation issues if typed in a function def.
236 # Global usage string, to avoid indentation issues if typed in a function def.
238 MAIN_USAGE = """
237 MAIN_USAGE = """
239 %prog [options] file_to_run
238 %prog [options] file_to_run
240
239
241 This is an interface to the various interactive runners available in this
240 This is an interface to the various interactive runners available in this
242 module. If you want to pass specific options to one of the runners, you need
241 module. If you want to pass specific options to one of the runners, you need
243 to first terminate the main options with a '--', and then provide the runner's
242 to first terminate the main options with a '--', and then provide the runner's
244 options. For example:
243 options. For example:
245
244
246 irunner.py --python -- --help
245 irunner.py --python -- --help
247
246
248 will pass --help to the python runner. Similarly,
247 will pass --help to the python runner. Similarly,
249
248
250 irunner.py --ipython -- --log test.log script.ipy
249 irunner.py --ipython -- --log test.log script.ipy
251
250
252 will run the script.ipy file under the IPython runner, logging all output into
251 will run the script.ipy file under the IPython runner, logging all output into
253 the test.log file.
252 the test.log file.
254 """
253 """
255
254
256 def main():
255 def main():
257 """Run as a command-line script."""
256 """Run as a command-line script."""
258
257
259 parser = optparse.OptionParser(usage=MAIN_USAGE)
258 parser = optparse.OptionParser(usage=MAIN_USAGE)
260 newopt = parser.add_option
259 newopt = parser.add_option
261 parser.set_defaults(mode='ipython')
260 parser.set_defaults(mode='ipython')
262 newopt('--ipython',action='store_const',dest='mode',const='ipython',
261 newopt('--ipython',action='store_const',dest='mode',const='ipython',
263 help='IPython interactive runner (default).')
262 help='IPython interactive runner (default).')
264 newopt('--python',action='store_const',dest='mode',const='python',
263 newopt('--python',action='store_const',dest='mode',const='python',
265 help='Python interactive runner.')
264 help='Python interactive runner.')
266 newopt('--sage',action='store_const',dest='mode',const='sage',
265 newopt('--sage',action='store_const',dest='mode',const='sage',
267 help='SAGE interactive runner - UNTESTED.')
266 help='SAGE interactive runner - UNTESTED.')
268
267
269 opts,args = parser.parse_args()
268 opts,args = parser.parse_args()
270 runners = dict(ipython=IPythonRunner,
269 runners = dict(ipython=IPythonRunner,
271 python=PythonRunner,
270 python=PythonRunner,
272 sage=SAGERunner)
271 sage=SAGERunner)
273 runners[opts.mode]().main(args)
272 runners[opts.mode]().main(args)
274
273
275 if __name__ == '__main__':
274 if __name__ == '__main__':
276 main()
275 main()
General Comments 0
You need to be logged in to leave comments. Login now