##// END OF EJS Templates
Minor cleanup - leftover debug stuff
fperez -
Show More
@@ -1,276 +1,275 b''
1 1 #!/usr/bin/env python
2 2 """Module for interactively running scripts.
3 3
4 4 This module implements classes for interactively running scripts written for
5 5 any system with a prompt which can be matched by a regexp suitable for
6 6 pexpect. It can be used to run as if they had been typed up interactively, an
7 7 arbitrary series of commands for the target system.
8 8
9 9 The module includes classes ready for IPython (with the default prompts),
10 10 plain Python and SAGE, but making a new one is trivial. To see how to use it,
11 11 simply run the module as a script:
12 12
13 13 ./irunner.py --help
14 14
15 15
16 16 This is an extension of Ken Schutte <kschutte-AT-csail.mit.edu>'s script
17 17 contributed on the ipython-user list:
18 18
19 19 http://scipy.net/pipermail/ipython-user/2006-May/001705.html
20 20
21 21
22 22 NOTES:
23 23
24 24 - This module requires pexpect, available in most linux distros, or which can
25 25 be downloaded from
26 26
27 27 http://pexpect.sourceforge.net
28 28
29 29 - Because pexpect only works under Unix or Windows-Cygwin, this has the same
30 30 limitations. This means that it will NOT work under native windows Python.
31 31 """
32 32
33 33 # Stdlib imports
34 34 import optparse
35 35 import sys
36 36
37 37 # Third-party modules.
38 38 import pexpect
39 39
40 40 # Global usage strings, to avoid indentation issues when typing it below.
41 41 USAGE = """
42 42 Interactive script runner, type: %s
43 43
44 44 runner [opts] script_name
45 45 """
46 46
47 47 # The generic runner class
48 48 class InteractiveRunner(object):
49 49 """Class to run a sequence of commands through an interactive program."""
50 50
51 51 def __init__(self,program,prompts,args=None):
52 52 """Construct a runner.
53 53
54 54 Inputs:
55 55
56 56 - program: command to execute the given program.
57 57
58 58 - prompts: a list of patterns to match as valid prompts, in the
59 59 format used by pexpect. This basically means that it can be either
60 60 a string (to be compiled as a regular expression) or a list of such
61 61 (it must be a true list, as pexpect does type checks).
62 62
63 63 If more than one prompt is given, the first is treated as the main
64 64 program prompt and the others as 'continuation' prompts, like
65 65 python's. This means that blank lines in the input source are
66 66 ommitted when the first prompt is matched, but are NOT ommitted when
67 67 the continuation one matches, since this is how python signals the
68 68 end of multiline input interactively.
69 69
70 70 Optional inputs:
71 71
72 72 - args(None): optional list of strings to pass as arguments to the
73 73 child program.
74 74 """
75 75
76 76 self.program = program
77 77 self.prompts = prompts
78 78 if args is None: args = []
79 79 self.args = args
80 80
81 81 def run_file(self,fname,interact=False):
82 82 """Run the given file interactively.
83 83
84 84 Inputs:
85 85
86 86 -fname: name of the file to execute.
87 87
88 88 See the run_source docstring for the meaning of the optional
89 89 arguments."""
90 90
91 91 fobj = open(fname,'r')
92 92 try:
93 93 self.run_source(fobj,interact)
94 94 finally:
95 95 fobj.close()
96 96
97 97 def run_source(self,source,interact=False):
98 98 """Run the given source code interactively.
99 99
100 100 Inputs:
101 101
102 102 - source: a string of code to be executed, or an open file object we
103 103 can iterate over.
104 104
105 105 Optional inputs:
106 106
107 107 - interact(False): if true, start to interact with the running
108 108 program at the end of the script. Otherwise, just exit.
109 109 """
110 110
111 111 # if the source is a string, chop it up in lines so we can iterate
112 112 # over it just as if it were an open file.
113 113 if not isinstance(source,file):
114 114 source = source.splitlines(True)
115 115
116 116 # grab the true write method of stdout, in case anything later
117 117 # reassigns sys.stdout, so that we really are writing to the true
118 118 # stdout and not to something else.
119 119 write = sys.stdout.write
120 120
121 121 c = pexpect.spawn(self.program,self.args,timeout=None)
122 122
123 prompts = c.compile_pattern_list(self.prompts[0])
124 123 prompts = c.compile_pattern_list(self.prompts)
125 124
126 125 prompt_idx = c.expect_list(prompts)
127 126 # Flag whether the script ends normally or not, to know whether we can
128 127 # do anything further with the underlying process.
129 128 end_normal = True
130 129 for cmd in source:
131 130 # skip blank lines for all matches to the 'main' prompt, while the
132 131 # secondary prompts do not
133 132 if prompt_idx==0 and cmd.isspace():
134 133 continue
135 134
136 135 write(c.after)
137 136 c.send(cmd)
138 137 try:
139 138 prompt_idx = c.expect_list(prompts)
140 139 except pexpect.EOF:
141 140 # this will happen if the child dies unexpectedly
142 141 write(c.before)
143 142 end_normal = False
144 143 break
145 144 write(c.before)
146 145
147 146 if isinstance(source,file):
148 147 source.close()
149 148
150 149 if end_normal:
151 150 if interact:
152 151 c.send('\n')
153 152 print '<< Starting interactive mode >>',
154 153 try:
155 154 c.interact()
156 155 except OSError:
157 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 158 # space is there to make sure it gets printed, otherwise
160 159 # OS buffering sometimes just suppresses it.
161 160 write(' \n')
162 161 sys.stdout.flush()
163 162 else:
164 163 c.close()
165 164 else:
166 165 if interact:
167 166 e="Further interaction is not possible: child process is dead."
168 167 print >> sys.stderr, e
169 168
170 169 def main(self,argv=None):
171 170 """Run as a command-line script."""
172 171
173 172 parser = optparse.OptionParser(usage=USAGE % self.__class__.__name__)
174 173 newopt = parser.add_option
175 174 newopt('-i','--interact',action='store_true',default=False,
176 175 help='Interact with the program after the script is run.')
177 176
178 177 opts,args = parser.parse_args(argv)
179 178
180 179 if len(args) != 1:
181 180 print >> sys.stderr,"You must supply exactly one file to run."
182 181 sys.exit(1)
183 182
184 183 self.run_file(args[0],opts.interact)
185 184
186 185
187 186 # Specific runners for particular programs
188 187 class IPythonRunner(InteractiveRunner):
189 188 """Interactive IPython runner.
190 189
191 190 This initalizes IPython in 'nocolor' mode for simplicity. This lets us
192 191 avoid having to write a regexp that matches ANSI sequences, though pexpect
193 192 does support them. If anyone contributes patches for ANSI color support,
194 193 they will be welcome.
195 194
196 195 It also sets the prompts manually, since the prompt regexps for
197 196 pexpect need to be matched to the actual prompts, so user-customized
198 197 prompts would break this.
199 198 """
200 199
201 200 def __init__(self,program = 'ipython',args=None):
202 201 """New runner, optionally passing the ipython command to use."""
203 202
204 203 args0 = ['-colors','NoColor',
205 204 '-pi1','In [\\#]: ',
206 205 '-pi2',' .\\D.: ']
207 206 if args is None: args = args0
208 207 else: args = args0 + args
209 208 prompts = [r'In \[\d+\]: ',r' \.*: ']
210 209 InteractiveRunner.__init__(self,program,prompts,args)
211 210
212 211
213 212 class PythonRunner(InteractiveRunner):
214 213 """Interactive Python runner."""
215 214
216 215 def __init__(self,program='python',args=None):
217 216 """New runner, optionally passing the python command to use."""
218 217
219 218 prompts = [r'>>> ',r'\.\.\. ']
220 219 InteractiveRunner.__init__(self,program,prompts,args)
221 220
222 221
223 222 class SAGERunner(InteractiveRunner):
224 223 """Interactive SAGE runner.
225 224
226 225 XXX - This class is currently untested, meant for feedback from the SAGE
227 226 team. """
228 227
229 228 def __init__(self,program='sage',args=None):
230 229 """New runner, optionally passing the sage command to use."""
231 230 print 'XXX - This class is currently untested!!!'
232 231 print 'It is a placeholder, meant for feedback from the SAGE team.'
233 232
234 233 prompts = ['sage: ',r'\s*\.\.\. ']
235 234 InteractiveRunner.__init__(self,program,prompts,args)
236 235
237 236 # Global usage string, to avoid indentation issues if typed in a function def.
238 237 MAIN_USAGE = """
239 238 %prog [options] file_to_run
240 239
241 240 This is an interface to the various interactive runners available in this
242 241 module. If you want to pass specific options to one of the runners, you need
243 242 to first terminate the main options with a '--', and then provide the runner's
244 243 options. For example:
245 244
246 245 irunner.py --python -- --help
247 246
248 247 will pass --help to the python runner. Similarly,
249 248
250 249 irunner.py --ipython -- --log test.log script.ipy
251 250
252 251 will run the script.ipy file under the IPython runner, logging all output into
253 252 the test.log file.
254 253 """
255 254
256 255 def main():
257 256 """Run as a command-line script."""
258 257
259 258 parser = optparse.OptionParser(usage=MAIN_USAGE)
260 259 newopt = parser.add_option
261 260 parser.set_defaults(mode='ipython')
262 261 newopt('--ipython',action='store_const',dest='mode',const='ipython',
263 262 help='IPython interactive runner (default).')
264 263 newopt('--python',action='store_const',dest='mode',const='python',
265 264 help='Python interactive runner.')
266 265 newopt('--sage',action='store_const',dest='mode',const='sage',
267 266 help='SAGE interactive runner - UNTESTED.')
268 267
269 268 opts,args = parser.parse_args()
270 269 runners = dict(ipython=IPythonRunner,
271 270 python=PythonRunner,
272 271 sage=SAGERunner)
273 272 runners[opts.mode]().main(args)
274 273
275 274 if __name__ == '__main__':
276 275 main()
General Comments 0
You need to be logged in to leave comments. Login now