##// END OF EJS Templates
Changing input filtering to require whitespace separation between the initial command (alias, magic, autocall) and rest of line. ...
dan.milstein -
Show More
@@ -1,313 +1,313 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Classes and functions for prefiltering (transforming) a line of user input.
4 4 This module is responsible, primarily, for breaking the line up into useful
5 5 pieces and triggering the appropriate handlers in iplib to do the actual
6 6 transforming work.
7 7 """
8 8 __docformat__ = "restructuredtext en"
9 9
10 10 import re
11 11 import IPython.ipapi
12 12
13 13 class LineInfo(object):
14 14 """A single line of input and associated info.
15 15
16 16 Includes the following as properties:
17 17
18 18 line
19 19 The original, raw line
20 20
21 21 continue_prompt
22 22 Is this line a continuation in a sequence of multiline input?
23 23
24 24 pre
25 25 The initial esc character or whitespace.
26 26
27 27 preChar
28 28 The escape character(s) in pre or the empty string if there isn't one.
29 29 Note that '!!' is a possible value for preChar. Otherwise it will
30 30 always be a single character.
31 31
32 32 preWhitespace
33 33 The leading whitespace from pre if it exists. If there is a preChar,
34 34 this is just ''.
35 35
36 36 iFun
37 37 The 'function part', which is basically the maximal initial sequence
38 38 of valid python identifiers and the '.' character. This is what is
39 39 checked for alias and magic transformations, used for auto-calling,
40 40 etc.
41 41
42 42 theRest
43 43 Everything else on the line.
44 44 """
45 45 def __init__(self, line, continue_prompt):
46 46 self.line = line
47 47 self.continue_prompt = continue_prompt
48 48 self.pre, self.iFun, self.theRest = splitUserInput(line)
49 49
50 50 self.preChar = self.pre.strip()
51 51 if self.preChar:
52 52 self.preWhitespace = '' # No whitespace allowd before esc chars
53 53 else:
54 54 self.preWhitespace = self.pre
55 55
56 56 self._oinfo = None
57 57
58 58 def ofind(self, ip):
59 59 """Do a full, attribute-walking lookup of the iFun in the various
60 60 namespaces for the given IPython InteractiveShell instance.
61 61
62 62 Return a dict with keys: found,obj,ospace,ismagic
63 63
64 64 Note: can cause state changes because of calling getattr, but should
65 65 only be run if autocall is on and if the line hasn't matched any
66 66 other, less dangerous handlers.
67 67
68 68 Does cache the results of the call, so can be called multiple times
69 69 without worrying about *further* damaging state.
70 70 """
71 71 if not self._oinfo:
72 72 self._oinfo = ip._ofind(self.iFun)
73 73 return self._oinfo
74 74
75 75
76 76 def splitUserInput(line, pattern=None):
77 77 """Split user input into pre-char/whitespace, function part and rest.
78 78
79 79 Mostly internal to this module, but also used by iplib.expand_aliases,
80 80 which passes in a shell pattern.
81 81 """
82 82 # It seems to me that the shell splitting should be a separate method.
83 83
84 84 if not pattern:
85 85 pattern = line_split
86 86 match = pattern.match(line)
87 87 if not match:
88 88 #print "match failed for line '%s'" % line
89 89 try:
90 90 iFun,theRest = line.split(None,1)
91 91 except ValueError:
92 92 #print "split failed for line '%s'" % line
93 93 iFun,theRest = line,''
94 94 pre = re.match('^(\s*)(.*)',line).groups()[0]
95 95 else:
96 96 pre,iFun,theRest = match.groups()
97 97
98 98 # iFun has to be a valid python identifier, so it better be only pure
99 99 # ascii, no unicode:
100 100 try:
101 101 iFun = iFun.encode('ascii')
102 102 except UnicodeEncodeError:
103 103 theRest = iFun + u' ' + theRest
104 104 iFun = u''
105 105
106 106 #print 'line:<%s>' % line # dbg
107 107 #print 'pre <%s> iFun <%s> rest <%s>' % (pre,iFun.strip(),theRest) # dbg
108 return pre,iFun.strip(),theRest
108 return pre,iFun.strip(),theRest.lstrip()
109 109
110 110
111 111 # RegExp for splitting line contents into pre-char//first word-method//rest.
112 112 # For clarity, each group in on one line.
113 113
114 114 # WARNING: update the regexp if the escapes in iplib are changed, as they
115 115 # are hardwired in.
116 116
117 117 # Although it's not solely driven by the regex, note that:
118 118 # ,;/% only trigger if they are the first character on the line
119 119 # ! and !! trigger if they are first char(s) *or* follow an indent
120 120 # ? triggers as first or last char.
121 121
122 122 # The three parts of the regex are:
123 123 # 1) pre: pre_char *or* initial whitespace
124 124 # 2) iFun: first word/method (mix of \w and '.')
125 # 3) theRest: rest of line
125 # 3) theRest: rest of line (separated from iFun by space if non-empty)
126 126 line_split = re.compile(r'^([,;/%?]|!!?|\s*)'
127 r'\s*([\w\.]+)\s*'
128 r'(.*)$')
127 r'\s*([\w\.]+)'
128 r'(\s+.*$|$)')
129 129
130 130 shell_line_split = re.compile(r'^(\s*)(\S*\s*)(.*$)')
131 131
132 132 def prefilter(line_info, ip):
133 133 """Call one of the passed-in InteractiveShell's handler preprocessors,
134 134 depending on the form of the line. Return the results, which must be a
135 135 value, even if it's a blank ('')."""
136 136 # Note: the order of these checks does matter.
137 137 for check in [ checkEmacs,
138 138 checkIPyAutocall,
139 139 checkMultiLineShell,
140 140 checkEscChars,
141 141 checkAssignment,
142 142 checkAutomagic,
143 143 checkAlias,
144 144 checkPythonOps,
145 145 checkAutocall,
146 146 ]:
147 147 handler = check(line_info, ip)
148 148 if handler:
149 149 return handler(line_info)
150 150
151 151 return ip.handle_normal(line_info)
152 152
153 153 # Handler checks
154 154 #
155 155 # All have the same interface: they take a LineInfo object and a ref to the
156 156 # iplib.InteractiveShell object. They check the line to see if a particular
157 157 # handler should be called, and return either a handler or None. The
158 158 # handlers which they return are *bound* methods of the InteractiveShell
159 159 # object.
160 160 #
161 161 # In general, these checks should only take responsibility for their 'own'
162 162 # handler. If it doesn't get triggered, they should just return None and
163 163 # let the rest of the check sequence run.
164 164 def checkEmacs(l_info,ip):
165 165 "Emacs ipython-mode tags certain input lines."
166 166 if l_info.line.endswith('# PYTHON-MODE'):
167 167 return ip.handle_emacs
168 168 else:
169 169 return None
170 170
171 171 def checkIPyAutocall(l_info,ip):
172 172 "Instances of IPyAutocall in user_ns get autocalled immediately"
173 173 obj = ip.user_ns.get(l_info.iFun, None)
174 174 if isinstance(obj, IPython.ipapi.IPyAutocall):
175 175 obj.set_ip(ip.api)
176 176 return ip.handle_auto
177 177 else:
178 178 return None
179 179
180 180
181 181 def checkMultiLineShell(l_info,ip):
182 182 "Allow ! and !! in multi-line statements if multi_line_specials is on"
183 183 # Note that this one of the only places we check the first character of
184 184 # iFun and *not* the preChar. Also note that the below test matches
185 185 # both ! and !!.
186 186 if l_info.continue_prompt \
187 187 and ip.rc.multi_line_specials \
188 188 and l_info.iFun.startswith(ip.ESC_SHELL):
189 189 return ip.handle_shell_escape
190 190 else:
191 191 return None
192 192
193 193 def checkEscChars(l_info,ip):
194 194 """Check for escape character and return either a handler to handle it,
195 195 or None if there is no escape char."""
196 196 if l_info.line[-1] == ip.ESC_HELP \
197 197 and l_info.preChar != ip.ESC_SHELL \
198 198 and l_info.preChar != ip.ESC_SH_CAP:
199 199 # the ? can be at the end, but *not* for either kind of shell escape,
200 200 # because a ? can be a vaild final char in a shell cmd
201 201 return ip.handle_help
202 202 elif l_info.preChar in ip.esc_handlers:
203 203 return ip.esc_handlers[l_info.preChar]
204 204 else:
205 205 return None
206 206
207 207
208 208 def checkAssignment(l_info,ip):
209 209 """Check to see if user is assigning to a var for the first time, in
210 210 which case we want to avoid any sort of automagic / autocall games.
211 211
212 212 This allows users to assign to either alias or magic names true python
213 213 variables (the magic/alias systems always take second seat to true
214 214 python code). E.g. ls='hi', or ls,that=1,2"""
215 215 if l_info.theRest and l_info.theRest[0] in '=,':
216 216 return ip.handle_normal
217 217 else:
218 218 return None
219 219
220 220
221 221 def checkAutomagic(l_info,ip):
222 222 """If the iFun is magic, and automagic is on, run it. Note: normal,
223 223 non-auto magic would already have been triggered via '%' in
224 224 check_esc_chars. This just checks for automagic. Also, before
225 225 triggering the magic handler, make sure that there is nothing in the
226 226 user namespace which could shadow it."""
227 227 if not ip.rc.automagic or not hasattr(ip,'magic_'+l_info.iFun):
228 228 return None
229 229
230 230 # We have a likely magic method. Make sure we should actually call it.
231 231 if l_info.continue_prompt and not ip.rc.multi_line_specials:
232 232 return None
233 233
234 234 head = l_info.iFun.split('.',1)[0]
235 235 if isShadowed(head,ip):
236 236 return None
237 237
238 238 return ip.handle_magic
239 239
240 240
241 241 def checkAlias(l_info,ip):
242 242 "Check if the initital identifier on the line is an alias."
243 243 # Note: aliases can not contain '.'
244 244 head = l_info.iFun.split('.',1)[0]
245 245
246 246 if l_info.iFun not in ip.alias_table \
247 247 or head not in ip.alias_table \
248 248 or isShadowed(head,ip):
249 249 return None
250 250
251 251 return ip.handle_alias
252 252
253 253
254 254 def checkPythonOps(l_info,ip):
255 255 """If the 'rest' of the line begins with a function call or pretty much
256 256 any python operator, we should simply execute the line (regardless of
257 257 whether or not there's a possible autocall expansion). This avoids
258 258 spurious (and very confusing) geattr() accesses."""
259 259 if l_info.theRest and l_info.theRest[0] in '!=()<>,+*/%^&|':
260 260 return ip.handle_normal
261 261 else:
262 262 return None
263 263
264 264
265 265 def checkAutocall(l_info,ip):
266 266 "Check if the initial word/function is callable and autocall is on."
267 267 if not ip.rc.autocall:
268 268 return None
269 269
270 270 oinfo = l_info.ofind(ip) # This can mutate state via getattr
271 271 if not oinfo['found']:
272 272 return None
273 273
274 274 if callable(oinfo['obj']) \
275 275 and (not re_exclude_auto.match(l_info.theRest)) \
276 276 and re_fun_name.match(l_info.iFun):
277 277 #print 'going auto' # dbg
278 278 return ip.handle_auto
279 279 else:
280 280 #print 'was callable?', callable(l_info.oinfo['obj']) # dbg
281 281 return None
282 282
283 283 # RegExp to identify potential function names
284 284 re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$')
285 285
286 286 # RegExp to exclude strings with this start from autocalling. In
287 287 # particular, all binary operators should be excluded, so that if foo is
288 288 # callable, foo OP bar doesn't become foo(OP bar), which is invalid. The
289 289 # characters '!=()' don't need to be checked for, as the checkPythonChars
290 290 # routine explicitely does so, to catch direct calls and rebindings of
291 291 # existing names.
292 292
293 293 # Warning: the '-' HAS TO BE AT THE END of the first group, otherwise
294 294 # it affects the rest of the group in square brackets.
295 295 re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]'
296 296 r'|^is |^not |^in |^and |^or ')
297 297
298 298 # try to catch also methods for stuff in lists/tuples/dicts: off
299 299 # (experimental). For this to work, the line_split regexp would need
300 300 # to be modified so it wouldn't break things at '['. That line is
301 301 # nasty enough that I shouldn't change it until I can test it _well_.
302 302 #self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
303 303
304 304 # Handler Check Utilities
305 305 def isShadowed(identifier,ip):
306 306 """Is the given identifier defined in one of the namespaces which shadow
307 307 the alias and magic namespaces? Note that an identifier is different
308 308 than iFun, because it can not contain a '.' character."""
309 309 # This is much safer than calling ofind, which can change state
310 310 return (identifier in ip.user_ns \
311 311 or identifier in ip.internal_ns \
312 312 or identifier in ip.ns_table['builtin'])
313 313
@@ -1,201 +1,202 b''
1 1 """Test the various handlers which do the actual rewriting of the line."""
2 2
3 3 from StringIO import StringIO
4 4 import sys
5 5 sys.path.append('..')
6 6
7 7 failures = []
8 8 num_tests = 0
9 9
10 10 def run(tests):
11 11 """Loop through a list of (pre, post) inputs, where pre is the string
12 12 handed to ipython, and post is how that string looks after it's been
13 13 transformed (i.e. ipython's notion of _i)"""
14 14 for pre, post in tests:
15 15 global num_tests
16 16 num_tests += 1
17 17 ip.runlines(pre)
18 18 ip.runlines('_i') # Not sure why I need this...
19 19 actual = ip.user_ns['_i']
20 if actual != None: actual = actual.rstrip('\n')
20 if actual != None:
21 actual = actual.rstrip('\n')
21 22 if actual != post:
22 23 failures.append('Expected %r to become %r, found %r' % (
23 24 pre, post, actual))
24 25
25 26
26 27 # Shutdown stdout/stderr so that ipython isn't noisy during tests. Have to
27 28 # do this *before* importing IPython below.
28 29 #
29 30 # NOTE: this means that, if you stick print statements into code as part of
30 31 # debugging, you won't see the results (unless you comment out some of the
31 32 # below). I keep on doing this, so apparently it's easy. Or I am an idiot.
32 33 old_stdout = sys.stdout
33 34 old_stderr = sys.stderr
34 35
35 36 sys.stdout = StringIO()
36 37 sys.stderr = StringIO()
37 38
38 39 import IPython
39 40 import IPython.ipapi
40 41
41 42 IPython.Shell.start()
42 43 ip = IPython.ipapi.get()
43 44
44 45 class CallableIndexable(object):
45 46 def __getitem__(self, idx): return True
46 47 def __call__(self, *args, **kws): return True
47 48
48 49
49 50 try:
50 51 # alias expansion
51 52
52 53 # We're using 'true' as our syscall of choice because it doesn't
53 54 # write anything to stdout.
54 55
55 56 # Turn off actual execution of aliases, because it's noisy
56 old_system_cmd = ip.IP.system
57 ip.IP.system = lambda cmd: None
57 old_system_cmd = ip.system
58 ip.system = lambda cmd: None
58 59
59 60
60 61 ip.IP.alias_table['an_alias'] = (0, 'true')
61 62 # These are useful for checking a particular recursive alias issue
62 63 ip.IP.alias_table['top'] = (0, 'd:/cygwin/top')
63 64 ip.IP.alias_table['d'] = (0, 'true')
64 65 run([("an_alias", '_ip.system("true ")'), # alias
65 66 # Below: recursive aliases should expand whitespace-surrounded
66 67 # chars, *not* initial chars which happen to be aliases:
67 68 ("top", '_ip.system("d:/cygwin/top ")'),
68 69 ])
69 ip.IP.system = old_system_cmd
70 ip.system = old_system_cmd
70 71
71 72
72 73 call_idx = CallableIndexable()
73 74 ip.to_user_ns('call_idx')
74 75
75 76 # For many of the below, we're also checking that leading whitespace
76 77 # turns off the esc char, which it should unless there is a continuation
77 78 # line.
78 79 run([('"no change"', '"no change"'), # normal
79 80 ("!true", '_ip.system("true")'), # shell_escapes
80 81 ("!! true", '_ip.magic("sx true")'), # shell_escapes + magic
81 82 ("!!true", '_ip.magic("sx true")'), # shell_escapes + magic
82 83 ("%lsmagic", '_ip.magic("lsmagic ")'), # magic
83 84 ("lsmagic", '_ip.magic("lsmagic ")'), # magic
84 85 ("a = b # PYTHON-MODE", '_i'), # emacs -- avoids _in cache
85 86
86 87 # post-esc-char whitespace goes inside
87 88 ("! true", '_ip.system(" true")'),
88 89
89 90 # Leading whitespace generally turns off escape characters
90 91 (" ! true", ' ! true'),
91 92 (" !true", ' !true'),
92 93
93 94 # handle_help
94 95
95 96 # These are weak tests -- just looking at what the help handlers
96 97 # logs, which is not how it really does its work. But it still
97 98 # lets us check the key paths through the handler.
98 99
99 100 ("x=1 # what?", "x=1 # what?"), # no help if valid python
100 101 ("len?", "#?len"), # this is what help logs when it runs
101 102 ("len??", "#?len?"),
102 103 ("?len", "#?len"),
103 104 ])
104 105
105 106 # multi_line_specials
106 107 ip.options.multi_line_specials = 0
107 108 # W/ multi_line_specials off, leading ws kills esc chars/autoexpansion
108 109 run([
109 110 ('if 1:\n !true', 'if 1:\n !true'),
110 111 ('if 1:\n lsmagic', 'if 1:\n lsmagic'),
111 112 ('if 1:\n an_alias', 'if 1:\n an_alias'),
112 113 ])
113 114
114 115 ip.options.multi_line_specials = 1
115 116 # initial indents must be preserved.
116 117 run([
117 118 ('if 1:\n !true', 'if 1:\n _ip.system("true")'),
118 119 ('if 1:\n lsmagic', 'if 1:\n _ip.magic("lsmagic ")'),
119 120 ('if 1:\n an_alias', 'if 1:\n _ip.system("true ")'),
120 121 # Weird one
121 122 ('if 1:\n !!true', 'if 1:\n _ip.magic("sx true")'),
122 123
123 124
124 125 # Even with m_l_s on, all esc_chars except ! are off
125 126 ('if 1:\n %lsmagic', 'if 1:\n %lsmagic'),
126 127 ('if 1:\n /fun 1 2', 'if 1:\n /fun 1 2'),
127 128 ('if 1:\n ;fun 1 2', 'if 1:\n ;fun 1 2'),
128 129 ('if 1:\n ,fun 1 2', 'if 1:\n ,fun 1 2'),
129 130 ('if 1:\n ?fun 1 2', 'if 1:\n ?fun 1 2'),
130 131 # What about !!
131 132 ])
132 133
133 134
134 135 # Objects which are instances of IPyAutocall are *always* autocalled
135 136 import IPython.ipapi
136 137 class Autocallable(IPython.ipapi.IPyAutocall):
137 138 def __call__(self):
138 139 return "called"
139 140
140 141 autocallable = Autocallable()
141 142 ip.to_user_ns('autocallable')
142 143
143 144 # auto
144 145 ip.options.autocall = 0
145 146 # Only explicit escapes or instances of IPyAutocallable should get
146 147 # expanded
147 148 run([
148 149 ('len "abc"', 'len "abc"'),
149 150 ('autocallable', 'autocallable()'),
150 151 (",list 1 2 3", 'list("1", "2", "3")'),
151 152 (";list 1 2 3", 'list("1 2 3")'),
152 153 ("/len range(1,4)", 'len(range(1,4))'),
153 154 ])
154 155 ip.options.autocall = 1
155 156 run([
156 157 (",list 1 2 3", 'list("1", "2", "3")'),
157 158 (";list 1 2 3", 'list("1 2 3")'),
158 159 ("/len range(1,4)", 'len(range(1,4))'),
159 160 ('len "abc"', 'len("abc")'),
160 161 ('len "abc";', 'len("abc");'), # ; is special -- moves out of parens
161 162 # Autocall is turned off if first arg is [] and the object
162 163 # is both callable and indexable. Like so:
163 164 ('len [1,2]', 'len([1,2])'), # len doesn't support __getitem__...
164 165 ('call_idx [1]', 'call_idx [1]'), # call_idx *does*..
165 166 ('call_idx 1', 'call_idx(1)'),
166 167 ('len', 'len '), # only at 2 does it auto-call on single args
167 168 ])
168 169
169 170 ip.options.autocall = 2
170 171 run([
171 172 (",list 1 2 3", 'list("1", "2", "3")'),
172 173 (";list 1 2 3", 'list("1 2 3")'),
173 174 ("/len range(1,4)", 'len(range(1,4))'),
174 175 ('len "abc"', 'len("abc")'),
175 176 ('len "abc";', 'len("abc");'),
176 177 ('len [1,2]', 'len([1,2])'),
177 178 ('call_idx [1]', 'call_idx [1]'),
178 179 ('call_idx 1', 'call_idx(1)'),
179 180 # This is what's different:
180 181 ('len', 'len()'), # only at 2 does it auto-call on single args
181 182 ])
182 183 ip.options.autocall = 1
183 184
184 185 # Ignoring handle_emacs, 'cause it doesn't do anything.
185 186 finally:
186 187 sys.stdout = old_stdout
187 188 sys.stderr = old_stderr
188 189
189 190
190 191
191 192
192 193 num_f = len(failures)
193 194 #if verbose:
194 195 # print
195 196
196 197
197 198 print "%s tests run, %s failure%s" % (num_tests,
198 199 num_f,
199 200 num_f != 1 and "s" or "")
200 201 for f in failures:
201 202 print f
@@ -1,428 +1,443 b''
1 1 """
2 2 Test which prefilter transformations get called for various input lines.
3 3 Note that this does *not* test the transformations themselves -- it's just
4 4 verifying that a particular combination of, e.g. config options and escape
5 5 chars trigger the proper handle_X transform of the input line.
6 6
7 7 Usage: run from the command line with *normal* python, not ipython:
8 8 > python test_prefilter.py
9 9
10 10 Fairly quiet output by default. Pass in -v to get everyone's favorite dots.
11 11 """
12 12
13 13 # The prefilter always ends in a call to some self.handle_X method. We swap
14 14 # all of those out so that we can capture which one was called.
15 15
16 16 import sys
17 17 sys.path.append('..')
18 18 import IPython
19 19 import IPython.ipapi
20 20
21 21 verbose = False
22 22 if len(sys.argv) > 1:
23 23 if sys.argv[1] == '-v':
24 24 sys.argv = sys.argv[:-1] # IPython is confused by -v, apparently
25 25 verbose = True
26 26
27 27 IPython.Shell.start()
28 28
29 29 ip = IPython.ipapi.get()
30 30
31 31 # Collect failed tests + stats and print them at the end
32 32 failures = []
33 33 num_tests = 0
34 34
35 35 # Store the results in module vars as we go
36 36 last_line = None
37 37 handler_called = None
38 38 def install_mock_handler(name):
39 39 """Swap out one of the IP.handle_x methods with a function which can
40 40 record which handler was called and what line was produced. The mock
41 41 handler func always returns '', which causes ipython to cease handling
42 42 the string immediately. That way, that it doesn't echo output, raise
43 43 exceptions, etc. But do note that testing multiline strings thus gets
44 44 a bit hard."""
45 45 def mock_handler(self, line, continue_prompt=None,
46 46 pre=None,iFun=None,theRest=None,
47 47 obj=None):
48 48 #print "Inside %s with '%s'" % (name, line)
49 49 global last_line, handler_called
50 50 last_line = line
51 51 handler_called = name
52 52 return ''
53 53 mock_handler.name = name
54 54 setattr(IPython.iplib.InteractiveShell, name, mock_handler)
55 55
56 56 install_mock_handler('handle_normal')
57 57 install_mock_handler('handle_auto')
58 58 install_mock_handler('handle_magic')
59 59 install_mock_handler('handle_help')
60 60 install_mock_handler('handle_shell_escape')
61 61 install_mock_handler('handle_alias')
62 62 install_mock_handler('handle_emacs')
63 63
64 64
65 65 def reset_esc_handlers():
66 66 """The escape handlers are stored in a hash (as an attribute of the
67 67 InteractiveShell *instance*), so we have to rebuild that hash to get our
68 68 new handlers in there."""
69 69 s = ip.IP
70 70 s.esc_handlers = {s.ESC_PAREN : s.handle_auto,
71 71 s.ESC_QUOTE : s.handle_auto,
72 72 s.ESC_QUOTE2 : s.handle_auto,
73 73 s.ESC_MAGIC : s.handle_magic,
74 74 s.ESC_HELP : s.handle_help,
75 75 s.ESC_SHELL : s.handle_shell_escape,
76 76 s.ESC_SH_CAP : s.handle_shell_escape,
77 77 }
78 78 reset_esc_handlers()
79 79
80 80 # This is so I don't have to quote over and over. Gotta be a better way.
81 81 handle_normal = 'handle_normal'
82 82 handle_auto = 'handle_auto'
83 83 handle_magic = 'handle_magic'
84 84 handle_help = 'handle_help'
85 85 handle_shell_escape = 'handle_shell_escape'
86 86 handle_alias = 'handle_alias'
87 87 handle_emacs = 'handle_emacs'
88 88
89 89 def check(assertion, failure_msg):
90 90 """Check a boolean assertion and fail with a message if necessary. Store
91 91 an error essage in module-level failures list in case of failure. Print
92 92 '.' or 'F' if module var Verbose is true.
93 93 """
94 94 global num_tests
95 95 num_tests += 1
96 96 if assertion:
97 97 if verbose:
98 98 sys.stdout.write('.')
99 99 sys.stdout.flush()
100 100 else:
101 101 if verbose:
102 102 sys.stdout.write('F')
103 103 sys.stdout.flush()
104 104 failures.append(failure_msg)
105 105
106 106
107 107 def check_handler(expected_handler, line):
108 108 """Verify that the expected hander was called (for the given line,
109 109 passed in for failure reporting).
110 110
111 111 Pulled out to its own function so that tests which don't use
112 112 run_handler_tests can still take advantage of it."""
113 113 check(handler_called == expected_handler,
114 114 "Expected %s to be called for %s, "
115 115 "instead %s called" % (expected_handler,
116 116 repr(line),
117 117 handler_called))
118 118
119 119
120 120 def run_handler_tests(h_tests):
121 121 """Loop through a series of (input_line, handler_name) pairs, verifying
122 122 that, for each ip calls the given handler for the given line.
123 123
124 124 The verbose complaint includes the line passed in, so if that line can
125 125 include enough info to find the error, the tests are modestly
126 126 self-documenting.
127 127 """
128 128 for ln, expected_handler in h_tests:
129 129 global handler_called
130 130 handler_called = None
131 131 ip.runlines(ln)
132 132 check_handler(expected_handler, ln)
133 133
134 134 def run_one_test(ln, expected_handler):
135 135 run_handler_tests([(ln, expected_handler)])
136 136
137 137
138 138 # =========================================
139 139 # Tests
140 140 # =========================================
141 141
142 142
143 143 # Fundamental escape characters + whitespace & misc
144 144 # =================================================
145 145 esc_handler_tests = [
146 146 ( '?thing', handle_help, ),
147 147 ( 'thing?', handle_help ), # '?' can trail...
148 148 ( 'thing!', handle_normal), # but only '?' can trail
149 149 ( ' ?thing', handle_normal), # leading whitespace turns off esc chars
150 150 ( '!ls', handle_shell_escape),
151 151 ( '! true', handle_shell_escape),
152 152 ( '!! true', handle_shell_escape),
153 153 ( '%magic', handle_magic),
154 154 # XXX Possibly, add test for /,; once those are unhooked from %autocall
155 155 ( 'emacs_mode # PYTHON-MODE', handle_emacs ),
156 156 ( ' ', handle_normal),
157
157 158 # Trailing qmark combos. Odd special cases abound
158 ( '!thing?', handle_shell_escape), # trailing '?' loses to shell esc
159 ( '!thing ?', handle_shell_escape),
160 ( '!!thing?', handle_shell_escape),
159
160 # The key is: we don't want the trailing ? to trigger help if it's a
161 # part of a shell glob (like, e.g. '!ls file.?'). Instead, we want the
162 # shell handler to be called. Due to subtleties of the input string
163 # parsing, however, we only call the shell handler if the trailing ? is
164 # part of something whitespace-separated from the !cmd. See examples.
165 ( '!thing?', handle_help),
166 ( '!thing arg?', handle_shell_escape),
167 ( '!!thing?', handle_help),
168 ( '!!thing arg?', handle_shell_escape),
169
170 # For all other leading esc chars, we always trigger help
161 171 ( '%cmd?', handle_help),
172 ( '%cmd ?', handle_help),
173 ( '/cmd?', handle_help),
162 174 ( '/cmd?', handle_help),
163 175 ( ';cmd?', handle_help),
164 176 ( ',cmd?', handle_help),
165 177 ]
166 178 run_handler_tests(esc_handler_tests)
167 179
168 180
169 181
170 182 # Shell Escapes in Multi-line statements
171 183 # ======================================
172 184 #
173 185 # We can't test this via runlines, since the hacked-over-for-testing
174 186 # handlers all return None, so continue_prompt never becomes true. Instead
175 187 # we drop into prefilter directly and pass in continue_prompt.
176 188
177 189 old_mls = ip.options.multi_line_specials
178 190 for ln in [ ' !ls $f multi_line_specials %s',
179 191 ' !!ls $f multi_line_specials %s', # !! escapes work on mls
180 192 # Trailing ? doesn't trigger help:
181 193 ' !ls $f multi_line_specials %s ?',
182 194 ' !!ls $f multi_line_specials %s ?',
183 195 ]:
184 196 ip.options.multi_line_specials = 1
185 197 on_ln = ln % 'on'
186 198 ignore = ip.IP.prefilter(on_ln, continue_prompt=True)
187 199 check_handler(handle_shell_escape, on_ln)
188 200
189 201 ip.options.multi_line_specials = 0
190 202 off_ln = ln % 'off'
191 203 ignore = ip.IP.prefilter(off_ln, continue_prompt=True)
192 204 check_handler(handle_normal, off_ln)
193 205
194 206 ip.options.multi_line_specials = old_mls
195 207
196 208
197 209 # Automagic
198 210 # =========
199 211
200 212 # Pick one magic fun and one non_magic fun, make sure both exist
201 213 assert hasattr(ip.IP, "magic_cpaste")
202 214 assert not hasattr(ip.IP, "magic_does_not_exist")
203 215 ip.options.autocall = 0 # gotta have this off to get handle_normal
204 216 ip.options.automagic = 0
205 217 run_handler_tests([
206 218 # Without automagic, only shows up with explicit escape
207 219 ( 'cpaste', handle_normal),
208 220 ( '%cpaste', handle_magic),
209 221 ( '%does_not_exist', handle_magic),
210 222 ])
211 223 ip.options.automagic = 1
212 224 run_handler_tests([
213 225 ( 'cpaste', handle_magic),
214 226 ( '%cpaste', handle_magic),
215 227 ( 'does_not_exist', handle_normal),
216 228 ( '%does_not_exist', handle_magic),
217 229 ( 'cd /', handle_magic),
218 230 ( 'cd = 2', handle_normal),
231 ( 'r', handle_magic),
232 ( 'r thing', handle_magic),
233 ( 'r"str"', handle_normal),
219 234 ])
220 235
221 236 # If next elt starts with anything that could be an assignment, func call,
222 237 # etc, we don't call the magic func, unless explicitly escaped to do so.
223 238 #magic_killing_tests = []
224 239 #for c in list('!=()<>,'):
225 240 # magic_killing_tests.append(('cpaste %s killed_automagic' % c, handle_normal))
226 241 # magic_killing_tests.append(('%%cpaste %s escaped_magic' % c, handle_magic))
227 242 #run_handler_tests(magic_killing_tests)
228 243
229 244 # magic on indented continuation lines -- on iff multi_line_specials == 1
230 245 ip.options.multi_line_specials = 0
231 246 ln = ' cpaste multi_line off kills magic'
232 247 ignore = ip.IP.prefilter(ln, continue_prompt=True)
233 248 check_handler(handle_normal, ln)
234 249
235 250 ip.options.multi_line_specials = 1
236 251 ln = ' cpaste multi_line on enables magic'
237 252 ignore = ip.IP.prefilter(ln, continue_prompt=True)
238 253 check_handler(handle_magic, ln)
239 254
240 255 # user namespace shadows the magic one unless shell escaped
241 256 ip.user_ns['cpaste'] = 'user_ns'
242 257 run_handler_tests([
243 258 ( 'cpaste', handle_normal),
244 259 ( '%cpaste', handle_magic)])
245 260 del ip.user_ns['cpaste']
246 261
247 262
248 263
249 264 # Check for !=() turning off .ofind
250 265 # =================================
251 266 class AttributeMutator(object):
252 267 """A class which will be modified on attribute access, to test ofind"""
253 268 def __init__(self):
254 269 self.called = False
255 270
256 271 def getFoo(self): self.called = True
257 272 foo = property(getFoo)
258 273
259 274 attr_mutator = AttributeMutator()
260 275 ip.to_user_ns('attr_mutator')
261 276
262 277 ip.options.autocall = 1
263 278
264 279 run_one_test('attr_mutator.foo should mutate', handle_normal)
265 280 check(attr_mutator.called, 'ofind should be called in absence of assign characters')
266 281
267 282 for c in list('!=()<>+*/%^&|'):
268 283 attr_mutator.called = False
269 284 run_one_test('attr_mutator.foo %s should *not* mutate' % c, handle_normal)
270 285 run_one_test('attr_mutator.foo%s should *not* mutate' % c, handle_normal)
271 286
272 287 check(not attr_mutator.called,
273 288 'ofind should not be called near character %s' % c)
274 289
275 290
276 291
277 292 # Alias expansion
278 293 # ===============
279 294
280 295 # With autocall on or off, aliases should be shadowed by user, internal and
281 296 # __builtin__ namespaces
282 297 #
283 298 # XXX Can aliases have '.' in their name? With autocall off, that works,
284 299 # with autocall on, it doesn't. Hmmm.
285 300 import __builtin__
286 301 for ac_state in [0,1]:
287 302 ip.options.autocall = ac_state
288 303 ip.IP.alias_table['alias_cmd'] = 'alias_result'
289 304 ip.IP.alias_table['alias_head.with_dot'] = 'alias_result'
290 305 run_handler_tests([
291 306 ("alias_cmd", handle_alias),
292 307 # XXX See note above
293 308 #("alias_head.with_dot unshadowed, autocall=%s" % ac_state, handle_alias),
294 309 ("alias_cmd.something aliases must match whole expr", handle_normal),
295 310 ("alias_cmd /", handle_alias),
296 311 ])
297 312
298 313 for ns in [ip.user_ns, ip.IP.internal_ns, __builtin__.__dict__ ]:
299 314 ns['alias_cmd'] = 'a user value'
300 315 ns['alias_head'] = 'a user value'
301 316 run_handler_tests([
302 317 ("alias_cmd", handle_normal),
303 318 ("alias_head.with_dot", handle_normal)])
304 319 del ns['alias_cmd']
305 320 del ns['alias_head']
306 321
307 322 ip.options.autocall = 1
308 323
309 324
310 325
311 326
312 327 # Autocall
313 328 # ========
314 329
315 330 # For all the tests below, 'len' is callable / 'thing' is not
316 331
317 332 # Objects which are instances of IPyAutocall are *always* autocalled
318 333 import IPython.ipapi
319 334 class Autocallable(IPython.ipapi.IPyAutocall):
320 335 def __call__(self):
321 336 return "called"
322 337
323 338 autocallable = Autocallable()
324 339 ip.to_user_ns('autocallable')
325 340
326 341
327 342 # First, with autocalling fully off
328 343 ip.options.autocall = 0
329 344 run_handler_tests( [
330 345 # With no escapes, no autocalling expansions happen, callable or not,
331 346 # unless the obj extends IPyAutocall
332 347 ( 'len autocall_0', handle_normal),
333 348 ( 'thing autocall_0', handle_normal),
334 349 ( 'autocallable', handle_auto),
335 350
336 351 # With explicit escapes, callable and non-callables both get expanded,
337 352 # regardless of the %autocall setting:
338 353 ( '/len autocall_0', handle_auto),
339 354 ( ',len autocall_0 b0', handle_auto),
340 355 ( ';len autocall_0 b0', handle_auto),
341 356
342 357 ( '/thing autocall_0', handle_auto),
343 358 ( ',thing autocall_0 b0', handle_auto),
344 359 ( ';thing autocall_0 b0', handle_auto),
345 360
346 361 # Explicit autocall should not trigger if there is leading whitespace
347 362 ( ' /len autocall_0', handle_normal),
348 363 ( ' ;len autocall_0', handle_normal),
349 364 ( ' ,len autocall_0', handle_normal),
350 365 ( ' / len autocall_0', handle_normal),
351 366
352 367 # But should work if the whitespace comes after the esc char
353 368 ( '/ len autocall_0', handle_auto),
354 369 ( '; len autocall_0', handle_auto),
355 370 ( ', len autocall_0', handle_auto),
356 371 ( '/ len autocall_0', handle_auto),
357 372 ])
358 373
359 374
360 375 # Now, with autocall in default, 'smart' mode
361 376 ip.options.autocall = 1
362 377 run_handler_tests( [
363 378 # Autocalls without escapes -- only expand if it's callable
364 379 ( 'len a1', handle_auto),
365 380 ( 'thing a1', handle_normal),
366 381 ( 'autocallable', handle_auto),
367 382
368 383 # As above, all explicit escapes generate auto-calls, callable or not
369 384 ( '/len a1', handle_auto),
370 385 ( ',len a1 b1', handle_auto),
371 386 ( ';len a1 b1', handle_auto),
372 387 ( '/thing a1', handle_auto),
373 388 ( ',thing a1 b1', handle_auto),
374 389 ( ';thing a1 b1', handle_auto),
375 390
376 391 # Autocalls only happen on things which look like funcs, even if
377 392 # explicitly requested. Which, in this case means they look like a
378 393 # sequence of identifiers and . attribute references. Possibly the
379 394 # second of these two should trigger handle_auto. But not for now.
380 395 ( '"abc".join range(4)', handle_normal),
381 396 ( '/"abc".join range(4)', handle_normal),
382 397 ])
383 398
384 399
385 400 # No tests for autocall = 2, since the extra magic there happens inside the
386 401 # handle_auto function, which our test doesn't examine.
387 402
388 403 # Note that we leave autocall in default, 1, 'smart' mode
389 404
390 405
391 406 # Autocall / Binary operators
392 407 # ==========================
393 408
394 409 # Even with autocall on, 'len in thing' won't transform.
395 410 # But ';len in thing' will
396 411
397 412 # Note, the tests below don't check for multi-char ops. It could.
398 413
399 414 # XXX % is a binary op and should be in the list, too, but fails
400 415 bin_ops = list(r'<>,&^|*/+-') + 'is not in and or'.split()
401 416 bin_tests = []
402 417 for b in bin_ops:
403 418 bin_tests.append(('len %s binop_autocall' % b, handle_normal))
404 419 bin_tests.append((';len %s binop_autocall' % b, handle_auto))
405 420 bin_tests.append((',len %s binop_autocall' % b, handle_auto))
406 421 bin_tests.append(('/len %s binop_autocall' % b, handle_auto))
407 422
408 423 # Who loves auto-generating tests?
409 424 run_handler_tests(bin_tests)
410 425
411 426
412 427 # Possibly add tests for namespace shadowing (really ofind's business?).
413 428 #
414 429 # user > ipython internal > python builtin > alias > magic
415 430
416 431
417 432 # ============
418 433 # Test Summary
419 434 # ============
420 435 num_f = len(failures)
421 436 if verbose:
422 437 print
423 438 print "%s tests run, %s failure%s" % (num_tests,
424 439 num_f,
425 440 num_f != 1 and "s" or "")
426 441 for f in failures:
427 442 print f
428 443
General Comments 0
You need to be logged in to leave comments. Login now