##// END OF EJS Templates
Refined checking for assignment vs other python operators in the new input transformation/prefiltering system. This makes it once again possible for magics to do useful things which involve python operator characters (e.g. 'cd /'). Also added tests to verify this.
dan.milstein -
Show More
@@ -1,298 +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 108 return pre,iFun.strip(),theRest
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 125 # 3) theRest: rest of line
126 126 line_split = re.compile(r'^([,;/%?]|!!?|\s*)'
127 127 r'\s*([\w\.]+)\s*'
128 128 r'(.*)$')
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 checkPythonChars,
141 checkAssignment,
142 142 checkAutomagic,
143 checkPythonOps,
143 144 checkAlias,
144 145 checkAutocall,
145 146 ]:
146 147 handler = check(line_info, ip)
147 148 if handler:
148 149 return handler(line_info)
149 150
150 151 return ip.handle_normal(line_info)
151 152
152 153 # Handler checks
153 154 #
154 155 # All have the same interface: they take a LineInfo object and a ref to the
155 156 # iplib.InteractiveShell object. They check the line to see if a particular
156 157 # handler should be called, and return either a handler or None. The
157 158 # handlers which they return are *bound* methods of the InteractiveShell
158 159 # object.
159 160 #
160 161 # In general, these checks should only take responsibility for their 'own'
161 162 # handler. If it doesn't get triggered, they should just return None and
162 163 # let the rest of the check sequence run.
163 164 def checkEmacs(l_info,ip):
164 165 "Emacs ipython-mode tags certain input lines."
165 166 if l_info.line.endswith('# PYTHON-MODE'):
166 167 return ip.handle_emacs
167 168 else:
168 169 return None
169 170
170 171 def checkIPyAutocall(l_info,ip):
171 172 "Instances of IPyAutocall in user_ns get autocalled immediately"
172 173 obj = ip.user_ns.get(l_info.iFun, None)
173 174 if isinstance(obj, IPython.ipapi.IPyAutocall):
174 175 obj.set_ip(ip.api)
175 176 return ip.handle_auto
176 177 else:
177 178 return None
178 179
179 180
180 181 def checkMultiLineShell(l_info,ip):
181 182 "Allow ! and !! in multi-line statements if multi_line_specials is on"
182 183 # Note that this one of the only places we check the first character of
183 184 # iFun and *not* the preChar. Also note that the below test matches
184 185 # both ! and !!.
185 186 if l_info.continue_prompt \
186 187 and ip.rc.multi_line_specials \
187 188 and l_info.iFun.startswith(ip.ESC_SHELL):
188 189 return ip.handle_shell_escape
189 190 else:
190 191 return None
191 192
192 193 def checkEscChars(l_info,ip):
193 194 """Check for escape character and return either a handler to handle it,
194 195 or None if there is no escape char."""
195 196 if l_info.line[-1] == ip.ESC_HELP \
196 197 and l_info.preChar != ip.ESC_SHELL \
197 198 and l_info.preChar != ip.ESC_SH_CAP:
198 199 # the ? can be at the end, but *not* for either kind of shell escape,
199 200 # because a ? can be a vaild final char in a shell cmd
200 201 return ip.handle_help
201 202 elif l_info.preChar in ip.esc_handlers:
202 203 return ip.esc_handlers[l_info.preChar]
203 204 else:
204 205 return None
206
207
208 def checkAssignment(l_info,ip):
209 """Check to see if user is assigning to a var for the first time, in
210 which case we want to avoid any sort of automagic / autocall games.
205 211
206 def checkPythonChars(l_info,ip):
207 """If the 'rest' of the line begins with an (in)equality, assginment,
208 function call or tuple comma, we should simply execute the line
209 (regardless of whether or not there's a possible alias, automagic or
210 autocall expansion). This both avoids spurious geattr() accesses on
211 objects upon assignment, and also allows users to assign to either alias
212 or magic names true python variables (the magic/alias systems always
213 take second seat to true python code). E.g. ls='hi', or ls,that=1,2"""
214 if l_info.theRest and l_info.theRest[0] in '!=()<>,+*/%^&|':
212 This allows users to assign to either alias or magic names true python
213 variables (the magic/alias systems always take second seat to true
214 python code). E.g. ls='hi', or ls,that=1,2"""
215 if l_info.theRest and l_info.theRest[0] in '=,':
215 216 return ip.handle_normal
216 217 else:
217 218 return None
218 219
220
219 221 def checkAutomagic(l_info,ip):
220 222 """If the iFun is magic, and automagic is on, run it. Note: normal,
221 223 non-auto magic would already have been triggered via '%' in
222 check_esc_chars. This just checks for automagic."""
224 check_esc_chars. This just checks for automagic. Also, before
225 triggering the magic handler, make sure that there is nothing in the
226 user namespace which could shadow it."""
223 227 if not ip.rc.automagic or not hasattr(ip,'magic_'+l_info.iFun):
224 228 return None
225 229
226 230 # We have a likely magic method. Make sure we should actually call it.
227 231 if l_info.continue_prompt and not ip.rc.multi_line_specials:
228 232 return None
229 233
230 234 head = l_info.iFun.split('.',1)[0]
231 235 if isShadowed(head,ip):
232 236 return None
233 237
234 238 return ip.handle_magic
235 239
236
240
241 def checkPythonOps(l_info,ip):
242 """If the 'rest' of the line begins with a function call or pretty much
243 any python operator, we should simply execute the line (regardless of
244 whether or not there's a possible alias or autocall expansion). This
245 avoids spurious (and very confusing) geattr() accesses."""
246 if l_info.theRest and l_info.theRest[0] in '!=()<>,+*/%^&|':
247 return ip.handle_normal
248 else:
249 return None
250
251
237 252 def checkAlias(l_info,ip):
238 253 "Check if the initital identifier on the line is an alias."
239 254 # Note: aliases can not contain '.'
240 255 head = l_info.iFun.split('.',1)[0]
241 256
242 257 if l_info.iFun not in ip.alias_table \
243 258 or head not in ip.alias_table \
244 259 or isShadowed(head,ip):
245 260 return None
246 261
247 262 return ip.handle_alias
248 263
249 264
250 265 def checkAutocall(l_info,ip):
251 266 "Check if the initial word/function is callable and autocall is on."
252 267 if not ip.rc.autocall:
253 268 return None
254 269
255 270 oinfo = l_info.ofind(ip) # This can mutate state via getattr
256 271 if not oinfo['found']:
257 272 return None
258 273
259 274 if callable(oinfo['obj']) \
260 275 and (not re_exclude_auto.match(l_info.theRest)) \
261 276 and re_fun_name.match(l_info.iFun):
262 277 #print 'going auto' # dbg
263 278 return ip.handle_auto
264 279 else:
265 280 #print 'was callable?', callable(l_info.oinfo['obj']) # dbg
266 281 return None
267 282
268 283 # RegExp to identify potential function names
269 284 re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$')
270 285
271 286 # RegExp to exclude strings with this start from autocalling. In
272 287 # particular, all binary operators should be excluded, so that if foo is
273 288 # callable, foo OP bar doesn't become foo(OP bar), which is invalid. The
274 289 # characters '!=()' don't need to be checked for, as the checkPythonChars
275 290 # routine explicitely does so, to catch direct calls and rebindings of
276 291 # existing names.
277 292
278 293 # Warning: the '-' HAS TO BE AT THE END of the first group, otherwise
279 294 # it affects the rest of the group in square brackets.
280 295 re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]'
281 296 r'|^is |^not |^in |^and |^or ')
282 297
283 298 # try to catch also methods for stuff in lists/tuples/dicts: off
284 299 # (experimental). For this to work, the line_split regexp would need
285 300 # to be modified so it wouldn't break things at '['. That line is
286 301 # nasty enough that I shouldn't change it until I can test it _well_.
287 302 #self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
288 303
289 304 # Handler Check Utilities
290 305 def isShadowed(identifier,ip):
291 306 """Is the given identifier defined in one of the namespaces which shadow
292 307 the alias and magic namespaces? Note that an identifier is different
293 308 than iFun, because it can not contain a '.' character."""
294 309 # This is much safer than calling ofind, which can change state
295 310 return (identifier in ip.user_ns \
296 311 or identifier in ip.internal_ns \
297 312 or identifier in ip.ns_table['builtin'])
298 313
@@ -1,424 +1,427 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 # Trailing qmark combos. Odd special cases abound
158 158 ( '!thing?', handle_shell_escape), # trailing '?' loses to shell esc
159 159 ( '!thing ?', handle_shell_escape),
160 160 ( '!!thing?', handle_shell_escape),
161 161 ( '%cmd?', handle_help),
162 162 ( '/cmd?', handle_help),
163 163 ( ';cmd?', handle_help),
164 164 ( ',cmd?', handle_help),
165 165 ]
166 166 run_handler_tests(esc_handler_tests)
167 167
168 168
169 169
170 170 # Shell Escapes in Multi-line statements
171 171 # ======================================
172 172 #
173 173 # We can't test this via runlines, since the hacked-over-for-testing
174 174 # handlers all return None, so continue_prompt never becomes true. Instead
175 175 # we drop into prefilter directly and pass in continue_prompt.
176 176
177 177 old_mls = ip.options.multi_line_specials
178 178 for ln in [ ' !ls $f multi_line_specials %s',
179 179 ' !!ls $f multi_line_specials %s', # !! escapes work on mls
180 180 # Trailing ? doesn't trigger help:
181 181 ' !ls $f multi_line_specials %s ?',
182 182 ' !!ls $f multi_line_specials %s ?',
183 183 ]:
184 184 ip.options.multi_line_specials = 1
185 185 on_ln = ln % 'on'
186 186 ignore = ip.IP.prefilter(on_ln, continue_prompt=True)
187 187 check_handler(handle_shell_escape, on_ln)
188 188
189 189 ip.options.multi_line_specials = 0
190 190 off_ln = ln % 'off'
191 191 ignore = ip.IP.prefilter(off_ln, continue_prompt=True)
192 192 check_handler(handle_normal, off_ln)
193 193
194 194 ip.options.multi_line_specials = old_mls
195 195
196 196
197 197 # Automagic
198 198 # =========
199 199
200 200 # Pick one magic fun and one non_magic fun, make sure both exist
201 201 assert hasattr(ip.IP, "magic_cpaste")
202 202 assert not hasattr(ip.IP, "magic_does_not_exist")
203 203 ip.options.autocall = 0 # gotta have this off to get handle_normal
204 204 ip.options.automagic = 0
205 205 run_handler_tests([
206 206 # Without automagic, only shows up with explicit escape
207 207 ( 'cpaste', handle_normal),
208 208 ( '%cpaste', handle_magic),
209 ( '%does_not_exist', handle_magic)
209 ( '%does_not_exist', handle_magic),
210 210 ])
211 211 ip.options.automagic = 1
212 212 run_handler_tests([
213 213 ( 'cpaste', handle_magic),
214 214 ( '%cpaste', handle_magic),
215 215 ( 'does_not_exist', handle_normal),
216 ( '%does_not_exist', handle_magic)])
216 ( '%does_not_exist', handle_magic),
217 ( 'cd /', handle_magic),
218 ( 'cd = 2', handle_normal),
219 ])
217 220
218 221 # If next elt starts with anything that could be an assignment, func call,
219 222 # etc, we don't call the magic func, unless explicitly escaped to do so.
220 magic_killing_tests = []
221 for c in list('!=()<>,'):
222 magic_killing_tests.append(('cpaste %s killed_automagic' % c, handle_normal))
223 magic_killing_tests.append(('%%cpaste %s escaped_magic' % c, handle_magic))
224 run_handler_tests(magic_killing_tests)
223 #magic_killing_tests = []
224 #for c in list('!=()<>,'):
225 # magic_killing_tests.append(('cpaste %s killed_automagic' % c, handle_normal))
226 # magic_killing_tests.append(('%%cpaste %s escaped_magic' % c, handle_magic))
227 #run_handler_tests(magic_killing_tests)
225 228
226 229 # magic on indented continuation lines -- on iff multi_line_specials == 1
227 230 ip.options.multi_line_specials = 0
228 231 ln = ' cpaste multi_line off kills magic'
229 232 ignore = ip.IP.prefilter(ln, continue_prompt=True)
230 233 check_handler(handle_normal, ln)
231 234
232 235 ip.options.multi_line_specials = 1
233 236 ln = ' cpaste multi_line on enables magic'
234 237 ignore = ip.IP.prefilter(ln, continue_prompt=True)
235 238 check_handler(handle_magic, ln)
236 239
237 240 # user namespace shadows the magic one unless shell escaped
238 241 ip.user_ns['cpaste'] = 'user_ns'
239 242 run_handler_tests([
240 243 ( 'cpaste', handle_normal),
241 244 ( '%cpaste', handle_magic)])
242 245 del ip.user_ns['cpaste']
243 246
244 247
245 248
246 249 # Check for !=() turning off .ofind
247 250 # =================================
248 251 class AttributeMutator(object):
249 252 """A class which will be modified on attribute access, to test ofind"""
250 253 def __init__(self):
251 254 self.called = False
252 255
253 256 def getFoo(self): self.called = True
254 257 foo = property(getFoo)
255 258
256 259 attr_mutator = AttributeMutator()
257 260 ip.to_user_ns('attr_mutator')
258 261
259 262 ip.options.autocall = 1
260 263
261 264 run_one_test('attr_mutator.foo should mutate', handle_normal)
262 265 check(attr_mutator.called, 'ofind should be called in absence of assign characters')
263 266
264 267 for c in list('!=()<>+*/%^&|'):
265 268 attr_mutator.called = False
266 269 run_one_test('attr_mutator.foo %s should *not* mutate' % c, handle_normal)
267 270 run_one_test('attr_mutator.foo%s should *not* mutate' % c, handle_normal)
268 271
269 272 check(not attr_mutator.called,
270 273 'ofind should not be called near character %s' % c)
271 274
272 275
273 276
274 277 # Alias expansion
275 278 # ===============
276 279
277 280 # With autocall on or off, aliases should be shadowed by user, internal and
278 281 # __builtin__ namespaces
279 282 #
280 283 # XXX Can aliases have '.' in their name? With autocall off, that works,
281 284 # with autocall on, it doesn't. Hmmm.
282 285 import __builtin__
283 286 for ac_state in [0,1]:
284 287 ip.options.autocall = ac_state
285 288 ip.IP.alias_table['alias_cmd'] = 'alias_result'
286 289 ip.IP.alias_table['alias_head.with_dot'] = 'alias_result'
287 290 run_handler_tests([
288 291 ("alias_cmd", handle_alias),
289 292 # XXX See note above
290 293 #("alias_head.with_dot unshadowed, autocall=%s" % ac_state, handle_alias),
291 294 ("alias_cmd.something aliases must match whole expr", handle_normal),
292 295 ])
293 296
294 297 for ns in [ip.user_ns, ip.IP.internal_ns, __builtin__.__dict__ ]:
295 298 ns['alias_cmd'] = 'a user value'
296 299 ns['alias_head'] = 'a user value'
297 300 run_handler_tests([
298 301 ("alias_cmd", handle_normal),
299 302 ("alias_head.with_dot", handle_normal)])
300 303 del ns['alias_cmd']
301 304 del ns['alias_head']
302 305
303 306 ip.options.autocall = 1
304 307
305 308
306 309
307 310
308 311 # Autocall
309 312 # ========
310 313
311 314 # For all the tests below, 'len' is callable / 'thing' is not
312 315
313 316 # Objects which are instances of IPyAutocall are *always* autocalled
314 317 import IPython.ipapi
315 318 class Autocallable(IPython.ipapi.IPyAutocall):
316 319 def __call__(self):
317 320 return "called"
318 321
319 322 autocallable = Autocallable()
320 323 ip.to_user_ns('autocallable')
321 324
322 325
323 326 # First, with autocalling fully off
324 327 ip.options.autocall = 0
325 328 run_handler_tests( [
326 329 # With no escapes, no autocalling expansions happen, callable or not,
327 330 # unless the obj extends IPyAutocall
328 331 ( 'len autocall_0', handle_normal),
329 332 ( 'thing autocall_0', handle_normal),
330 333 ( 'autocallable', handle_auto),
331 334
332 335 # With explicit escapes, callable and non-callables both get expanded,
333 336 # regardless of the %autocall setting:
334 337 ( '/len autocall_0', handle_auto),
335 338 ( ',len autocall_0 b0', handle_auto),
336 339 ( ';len autocall_0 b0', handle_auto),
337 340
338 341 ( '/thing autocall_0', handle_auto),
339 342 ( ',thing autocall_0 b0', handle_auto),
340 343 ( ';thing autocall_0 b0', handle_auto),
341 344
342 345 # Explicit autocall should not trigger if there is leading whitespace
343 346 ( ' /len autocall_0', handle_normal),
344 347 ( ' ;len autocall_0', handle_normal),
345 348 ( ' ,len autocall_0', handle_normal),
346 349 ( ' / len autocall_0', handle_normal),
347 350
348 351 # But should work if the whitespace comes after the esc char
349 352 ( '/ len autocall_0', handle_auto),
350 353 ( '; len autocall_0', handle_auto),
351 354 ( ', len autocall_0', handle_auto),
352 355 ( '/ len autocall_0', handle_auto),
353 356 ])
354 357
355 358
356 359 # Now, with autocall in default, 'smart' mode
357 360 ip.options.autocall = 1
358 361 run_handler_tests( [
359 362 # Autocalls without escapes -- only expand if it's callable
360 363 ( 'len a1', handle_auto),
361 364 ( 'thing a1', handle_normal),
362 365 ( 'autocallable', handle_auto),
363 366
364 367 # As above, all explicit escapes generate auto-calls, callable or not
365 368 ( '/len a1', handle_auto),
366 369 ( ',len a1 b1', handle_auto),
367 370 ( ';len a1 b1', handle_auto),
368 371 ( '/thing a1', handle_auto),
369 372 ( ',thing a1 b1', handle_auto),
370 373 ( ';thing a1 b1', handle_auto),
371 374
372 375 # Autocalls only happen on things which look like funcs, even if
373 376 # explicitly requested. Which, in this case means they look like a
374 377 # sequence of identifiers and . attribute references. Possibly the
375 378 # second of these two should trigger handle_auto. But not for now.
376 379 ( '"abc".join range(4)', handle_normal),
377 380 ( '/"abc".join range(4)', handle_normal),
378 381 ])
379 382
380 383
381 384 # No tests for autocall = 2, since the extra magic there happens inside the
382 385 # handle_auto function, which our test doesn't examine.
383 386
384 387 # Note that we leave autocall in default, 1, 'smart' mode
385 388
386 389
387 390 # Autocall / Binary operators
388 391 # ==========================
389 392
390 393 # Even with autocall on, 'len in thing' won't transform.
391 394 # But ';len in thing' will
392 395
393 396 # Note, the tests below don't check for multi-char ops. It could.
394 397
395 398 # XXX % is a binary op and should be in the list, too, but fails
396 399 bin_ops = list(r'<>,&^|*/+-') + 'is not in and or'.split()
397 400 bin_tests = []
398 401 for b in bin_ops:
399 402 bin_tests.append(('len %s binop_autocall' % b, handle_normal))
400 403 bin_tests.append((';len %s binop_autocall' % b, handle_auto))
401 404 bin_tests.append((',len %s binop_autocall' % b, handle_auto))
402 405 bin_tests.append(('/len %s binop_autocall' % b, handle_auto))
403 406
404 407 # Who loves auto-generating tests?
405 408 run_handler_tests(bin_tests)
406 409
407 410
408 411 # Possibly add tests for namespace shadowing (really ofind's business?).
409 412 #
410 413 # user > ipython internal > python builtin > alias > magic
411 414
412 415
413 416 # ============
414 417 # Test Summary
415 418 # ============
416 419 num_f = len(failures)
417 420 if verbose:
418 421 print
419 422 print "%s tests run, %s failure%s" % (num_tests,
420 423 num_f,
421 424 num_f != 1 and "s" or "")
422 425 for f in failures:
423 426 print f
424 427
General Comments 0
You need to be logged in to leave comments. Login now