##// END OF EJS Templates
- Small extension bug fix (irunner would fail to automatically recognize...
fperez -
Show More

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

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