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