##// END OF EJS Templates
Transformations for 'help?' syntax
Thomas Kluyver -
Show More
@@ -1,383 +1,434
1 1 import re
2 2 from typing import List, Tuple
3 3 from IPython.utils import tokenize2
4 4 from IPython.utils.tokenutil import generate_tokens
5 5
6 6 def leading_indent(lines):
7 7 """Remove leading indentation.
8 8
9 9 If the first line starts with a spaces or tabs, the same whitespace will be
10 10 removed from each following line.
11 11 """
12 12 m = re.match(r'^[ \t]+', lines[0])
13 13 if not m:
14 14 return lines
15 15 space = m.group(0)
16 16 n = len(space)
17 17 return [l[n:] if l.startswith(space) else l
18 18 for l in lines]
19 19
20 20 class PromptStripper:
21 21 """Remove matching input prompts from a block of input.
22 22
23 23 Parameters
24 24 ----------
25 25 prompt_re : regular expression
26 26 A regular expression matching any input prompt (including continuation)
27 27 initial_re : regular expression, optional
28 28 A regular expression matching only the initial prompt, but not continuation.
29 29 If no initial expression is given, prompt_re will be used everywhere.
30 30 Used mainly for plain Python prompts, where the continuation prompt
31 31 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
32 32
33 33 If initial_re and prompt_re differ,
34 34 only initial_re will be tested against the first line.
35 35 If any prompt is found on the first two lines,
36 36 prompts will be stripped from the rest of the block.
37 37 """
38 38 def __init__(self, prompt_re, initial_re=None):
39 39 self.prompt_re = prompt_re
40 40 self.initial_re = initial_re or prompt_re
41 41
42 42 def _strip(self, lines):
43 43 return [self.prompt_re.sub('', l, count=1) for l in lines]
44 44
45 45 def __call__(self, lines):
46 46 if self.initial_re.match(lines[0]) or \
47 47 (len(lines) > 1 and self.prompt_re.match(lines[1])):
48 48 return self._strip(lines)
49 49 return lines
50 50
51 51 classic_prompt = PromptStripper(
52 52 prompt_re=re.compile(r'^(>>>|\.\.\.)( |$)'),
53 53 initial_re=re.compile(r'^>>>( |$)')
54 54 )
55 55
56 56 ipython_prompt = PromptStripper(re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)'))
57 57
58 58 def cell_magic(lines):
59 59 if not lines[0].startswith('%%'):
60 60 return lines
61 61 if re.match('%%\w+\?', lines[0]):
62 62 # This case will be handled by help_end
63 63 return lines
64 64 magic_name, first_line = lines[0][2:].partition(' ')
65 65 body = '\n'.join(lines[1:])
66 66 return ['get_ipython().run_cell_magic(%r, %r, %r)' % (magic_name, first_line, body)]
67 67
68 68 line_transforms = [
69 69 leading_indent,
70 70 classic_prompt,
71 71 ipython_prompt,
72 72 cell_magic,
73 73 ]
74 74
75 75 # -----
76 76
77 def help_end(tokens_by_line):
78 pass
79
80 def escaped_command(tokens_by_line):
81 pass
82
83 77 def _find_assign_op(token_line):
84 78 # Find the first assignment in the line ('=' not inside brackets)
85 79 # We don't try to support multiple special assignment (a = b = %foo)
86 80 paren_level = 0
87 81 for i, ti in enumerate(token_line):
88 82 s = ti.string
89 83 if s == '=' and paren_level == 0:
90 84 return i
91 85 if s in '([{':
92 86 paren_level += 1
93 87 elif s in ')]}':
94 88 paren_level -= 1
95 89
96 90 def find_end_of_continued_line(lines, start_line: int):
97 91 """Find the last line of a line explicitly extended using backslashes.
98 92
99 93 Uses 0-indexed line numbers.
100 94 """
101 95 end_line = start_line
102 96 while lines[end_line].endswith('\\\n'):
103 97 end_line += 1
104 98 if end_line >= len(lines):
105 99 break
106 100 return end_line
107 101
108 102 def assemble_continued_line(lines, start: Tuple[int, int], end_line: int):
109 103 """Assemble pieces of a continued line into a single line.
110 104
111 105 Uses 0-indexed line numbers. *start* is (lineno, colno).
112 106 """
113 107 parts = [lines[start[0]][start[1]:]] + lines[start[0]+1:end_line+1]
114 108 return ' '.join([p[:-2] for p in parts[:-1]] # Strip backslash+newline
115 109 + [parts[-1][:-1]]) # Strip newline from last line
116 110
117 class MagicAssign:
118 @staticmethod
119 def find(tokens_by_line):
111 class TokenTransformBase:
112 # Lower numbers -> higher priority (for matches in the same location)
113 priority = 10
114
115 def sortby(self):
116 return self.start_line, self.start_col, self.priority
117
118 def __init__(self, start):
119 self.start_line = start[0] - 1 # Shift from 1-index to 0-index
120 self.start_col = start[1]
121
122 def transform(self, lines: List[str]):
123 raise NotImplementedError
124
125 class MagicAssign(TokenTransformBase):
126 @classmethod
127 def find(cls, tokens_by_line):
120 128 """Find the first magic assignment (a = %foo) in the cell.
121 129
122 130 Returns (line, column) of the % if found, or None. *line* is 1-indexed.
123 131 """
124 132 for line in tokens_by_line:
125 133 assign_ix = _find_assign_op(line)
126 134 if (assign_ix is not None) \
127 135 and (len(line) >= assign_ix + 2) \
128 136 and (line[assign_ix+1].string == '%') \
129 137 and (line[assign_ix+2].type == tokenize2.NAME):
130 return line[assign_ix+1].start
138 return cls(line[assign_ix+1].start)
131 139
132 @staticmethod
133 def transform(lines: List[str], start: Tuple[int, int]):
140 def transform(self, lines: List[str]):
134 141 """Transform a magic assignment found by find
135 142 """
136 start_line = start[0] - 1 # Shift from 1-index to 0-index
137 start_col = start[1]
138
143 start_line, start_col = self.start_line, self.start_col
139 144 lhs = lines[start_line][:start_col]
140 145 end_line = find_end_of_continued_line(lines, start_line)
141 146 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
142 147 assert rhs.startswith('%'), rhs
143 148 magic_name, _, args = rhs[1:].partition(' ')
144 149
145 150 lines_before = lines[:start_line]
146 151 call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args)
147 152 new_line = lhs + call + '\n'
148 153 lines_after = lines[end_line+1:]
149 154
150 155 return lines_before + [new_line] + lines_after
151 156
152 157
153 class SystemAssign:
154 @staticmethod
155 def find(tokens_by_line):
158 class SystemAssign(TokenTransformBase):
159 @classmethod
160 def find(cls, tokens_by_line):
156 161 """Find the first system assignment (a = !foo) in the cell.
157 162
158 163 Returns (line, column) of the ! if found, or None. *line* is 1-indexed.
159 164 """
160 165 for line in tokens_by_line:
161 166 assign_ix = _find_assign_op(line)
162 167 if (assign_ix is not None) \
163 168 and (len(line) >= assign_ix + 2) \
164 169 and (line[assign_ix + 1].type == tokenize2.ERRORTOKEN):
165 170 ix = assign_ix + 1
166 171
167 172 while ix < len(line) and line[ix].type == tokenize2.ERRORTOKEN:
168 173 if line[ix].string == '!':
169 return line[ix].start
174 return cls(line[ix].start)
170 175 elif not line[ix].string.isspace():
171 176 break
172 177 ix += 1
173 178
174 @staticmethod
175 def transform(lines: List[str], start: Tuple[int, int]):
179 def transform(self, lines: List[str]):
176 180 """Transform a system assignment found by find
177 181 """
178 start_line = start[0] - 1 # Shift from 1-index to 0-index
179 start_col = start[1]
182 start_line, start_col = self.start_line, self.start_col
180 183
181 184 lhs = lines[start_line][:start_col]
182 185 end_line = find_end_of_continued_line(lines, start_line)
183 186 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
184 187 assert rhs.startswith('!'), rhs
185 188 cmd = rhs[1:]
186 189
187 190 lines_before = lines[:start_line]
188 191 call = "get_ipython().getoutput({!r})".format(cmd)
189 192 new_line = lhs + call + '\n'
190 193 lines_after = lines[end_line + 1:]
191 194
192 195 return lines_before + [new_line] + lines_after
193 196
194 197 # The escape sequences that define the syntax transformations IPython will
195 198 # apply to user input. These can NOT be just changed here: many regular
196 199 # expressions and other parts of the code may use their hardcoded values, and
197 200 # for all intents and purposes they constitute the 'IPython syntax', so they
198 201 # should be considered fixed.
199 202
200 203 ESC_SHELL = '!' # Send line to underlying system shell
201 204 ESC_SH_CAP = '!!' # Send line to system shell and capture output
202 205 ESC_HELP = '?' # Find information about object
203 206 ESC_HELP2 = '??' # Find extra-detailed information about object
204 207 ESC_MAGIC = '%' # Call magic function
205 208 ESC_MAGIC2 = '%%' # Call cell-magic function
206 209 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
207 210 ESC_QUOTE2 = ';' # Quote all args as a single string, call
208 211 ESC_PAREN = '/' # Call first argument with rest of line as arguments
209 212
210 213 ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'}
211 214 ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately
212 215
213 216 def _make_help_call(target, esc, next_input=None):
214 217 """Prepares a pinfo(2)/psearch call from a target name and the escape
215 218 (i.e. ? or ??)"""
216 219 method = 'pinfo2' if esc == '??' \
217 220 else 'psearch' if '*' in target \
218 221 else 'pinfo'
219 222 arg = " ".join([method, target])
220 223 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
221 224 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
222 225 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
223 226 if next_input is None:
224 227 return 'get_ipython().run_line_magic(%r, %r)' % (t_magic_name, t_magic_arg_s)
225 228 else:
226 229 return 'get_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \
227 230 (next_input, t_magic_name, t_magic_arg_s)
228 231
229 232 def _tr_help(content):
230 233 "Translate lines escaped with: ?"
231 234 # A naked help line should just fire the intro help screen
232 235 if not content:
233 236 return 'get_ipython().show_usage()'
234 237
235 238 return _make_help_call(content, '?')
236 239
237 240 def _tr_help2(content):
238 241 "Translate lines escaped with: ??"
239 242 # A naked help line should just fire the intro help screen
240 243 if not content:
241 244 return 'get_ipython().show_usage()'
242 245
243 246 return _make_help_call(content, '??')
244 247
245 248 def _tr_magic(content):
246 249 "Translate lines escaped with: %"
247 250 name, _, args = content.partition(' ')
248 251 return 'get_ipython().run_line_magic(%r, %r)' % (name, args)
249 252
250 253 def _tr_quote(content):
251 254 "Translate lines escaped with: ,"
252 255 name, _, args = content.partition(' ')
253 256 return '%s("%s")' % (name, '", "'.join(args.split()) )
254 257
255 258 def _tr_quote2(content):
256 259 "Translate lines escaped with: ;"
257 260 name, _, args = content.partition(' ')
258 261 return '%s("%s")' % (name, args)
259 262
260 263 def _tr_paren(content):
261 264 "Translate lines escaped with: /"
262 265 name, _, args = content.partition(' ')
263 266 return '%s(%s)' % (name, ", ".join(args.split()))
264 267
265 268 tr = { ESC_SHELL : 'get_ipython().system({!r})'.format,
266 269 ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format,
267 270 ESC_HELP : _tr_help,
268 271 ESC_HELP2 : _tr_help2,
269 272 ESC_MAGIC : _tr_magic,
270 273 ESC_QUOTE : _tr_quote,
271 274 ESC_QUOTE2 : _tr_quote2,
272 275 ESC_PAREN : _tr_paren }
273 276
274 class EscapedCommand:
275 @staticmethod
276 def find(tokens_by_line):
277 class EscapedCommand(TokenTransformBase):
278 @classmethod
279 def find(cls, tokens_by_line):
277 280 """Find the first escaped command (%foo, !foo, etc.) in the cell.
278 281
279 282 Returns (line, column) of the escape if found, or None. *line* is 1-indexed.
280 283 """
281 284 for line in tokens_by_line:
282 285 ix = 0
283 286 while line[ix].type in {tokenize2.INDENT, tokenize2.DEDENT}:
284 287 ix += 1
285 288 if line[ix].string in ESCAPE_SINGLES:
286 return line[ix].start
289 return cls(line[ix].start)
287 290
288 @staticmethod
289 def transform(lines, start):
290 start_line = start[0] - 1 # Shift from 1-index to 0-index
291 start_col = start[1]
291 def transform(self, lines):
292 start_line, start_col = self.start_line, self.start_col
292 293
293 294 indent = lines[start_line][:start_col]
294 295 end_line = find_end_of_continued_line(lines, start_line)
295 296 line = assemble_continued_line(lines, (start_line, start_col), end_line)
296 297
297 298 if line[:2] in ESCAPE_DOUBLES:
298 299 escape, content = line[:2], line[2:]
299 300 else:
300 301 escape, content = line[:1], line[1:]
301 302 call = tr[escape](content)
302 303
303 304 lines_before = lines[:start_line]
304 305 new_line = indent + call + '\n'
305 306 lines_after = lines[end_line + 1:]
306 307
307 308 return lines_before + [new_line] + lines_after
308 309
310 _help_end_re = re.compile(r"""(%{0,2}
311 [a-zA-Z_*][\w*]* # Variable name
312 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
313 )
314 (\?\??)$ # ? or ??
315 """,
316 re.VERBOSE)
317
318 class HelpEnd(TokenTransformBase):
319 # This needs to be higher priority (lower number) than EscapedCommand so
320 # that inspecting magics (%foo?) works.
321 priority = 5
322
323 def __init__(self, start, q_locn):
324 super().__init__(start)
325 self.q_line = q_locn[0] - 1 # Shift from 1-indexed to 0-indexed
326 self.q_col = q_locn[1]
327
328 @classmethod
329 def find(cls, tokens_by_line):
330 for line in tokens_by_line:
331 # Last token is NEWLINE; look at last but one
332 if len(line) > 2 and line[-2].string == '?':
333 # Find the first token that's not INDENT/DEDENT
334 ix = 0
335 while line[ix].type in {tokenize2.INDENT, tokenize2.DEDENT}:
336 ix += 1
337 return cls(line[ix].start, line[-2].start)
338
339 def transform(self, lines):
340 piece = ''.join(lines[self.start_line:self.q_line+1])
341 indent, content = piece[:self.start_col], piece[self.start_col:]
342 lines_before = lines[:self.start_line]
343 lines_after = lines[self.q_line + 1:]
344
345 m = _help_end_re.search(content)
346 assert m is not None, content
347 target = m.group(1)
348 esc = m.group(3)
349
350 # If we're mid-command, put it back on the next prompt for the user.
351 next_input = None
352 if (not lines_before) and (not lines_after) \
353 and content.strip() != m.group(0):
354 next_input = content.rstrip('?\n')
355
356 call = _make_help_call(target, esc, next_input=next_input)
357 new_line = indent + call + '\n'
358
359 return lines_before + [new_line] + lines_after
360
309 361 def make_tokens_by_line(lines):
310 362 tokens_by_line = [[]]
311 363 for token in generate_tokens(iter(lines).__next__):
312 364 tokens_by_line[-1].append(token)
313 365 if token.type == tokenize2.NEWLINE:
314 366 tokens_by_line.append([])
315 367
316 368 return tokens_by_line
317 369
318 370 def show_linewise_tokens(s: str):
319 371 """For investigation"""
320 372 if not s.endswith('\n'):
321 373 s += '\n'
322 374 lines = s.splitlines(keepends=True)
323 375 for line in make_tokens_by_line(lines):
324 376 print("Line -------")
325 377 for tokinfo in line:
326 378 print(" ", tokinfo)
327 379
328 380 class TokenTransformers:
329 381 def __init__(self):
330 382 self.transformers = [
331 383 MagicAssign,
332 384 SystemAssign,
385 EscapedCommand,
386 HelpEnd,
333 387 ]
334 388
335 389 def do_one_transform(self, lines):
336 390 """Find and run the transform earliest in the code.
337 391
338 392 Returns (changed, lines).
339 393
340 394 This method is called repeatedly until changed is False, indicating
341 395 that all available transformations are complete.
342 396
343 397 The tokens following IPython special syntax might not be valid, so
344 398 the transformed code is retokenised every time to identify the next
345 399 piece of special syntax. Hopefully long code cells are mostly valid
346 400 Python, not using lots of IPython special syntax, so this shouldn't be
347 401 a performance issue.
348 402 """
349 403 tokens_by_line = make_tokens_by_line(lines)
350 404 candidates = []
351 for transformer in self.transformers:
352 locn = transformer.find(tokens_by_line)
353 if locn:
354 candidates.append((locn, transformer))
355
405 for transformer_cls in self.transformers:
406 transformer = transformer_cls.find(tokens_by_line)
407 if transformer:
408 candidates.append(transformer)
409
356 410 if not candidates:
357 411 # Nothing to transform
358 412 return False, lines
359
360 first_locn, transformer = min(candidates)
361 return True, transformer.transform(lines, first_locn)
413
414 transformer = min(candidates, key=TokenTransformBase.sortby)
415 return True, transformer.transform(lines)
362 416
363 417 def __call__(self, lines):
364 418 while True:
365 419 changed, lines = self.do_one_transform(lines)
366 420 if not changed:
367 421 return lines
368 422
369 def assign_from_system(tokens_by_line, lines):
370 pass
371
372 423
373 424 def transform_cell(cell):
374 425 if not cell.endswith('\n'):
375 426 cell += '\n' # Ensure every line has a newline
376 427 lines = cell.splitlines(keepends=True)
377 428 for transform in line_transforms:
378 429 #print(transform, lines)
379 430 lines = transform(lines)
380 431
381 432 lines = TokenTransformers()(lines)
382 433 for line in lines:
383 434 print('~~', line)
@@ -1,123 +1,179
1 1 import nose.tools as nt
2 2
3 3 from IPython.core import inputtransformer2 as ipt2
4 4 from IPython.core.inputtransformer2 import make_tokens_by_line
5 5
6 6 MULTILINE_MAGIC = ("""\
7 7 a = f()
8 8 %foo \\
9 9 bar
10 10 g()
11 """.splitlines(keepends=True), """\
11 """.splitlines(keepends=True), (2, 0), """\
12 12 a = f()
13 13 get_ipython().run_line_magic('foo', ' bar')
14 14 g()
15 15 """.splitlines(keepends=True))
16 16
17 17 INDENTED_MAGIC = ("""\
18 18 for a in range(5):
19 19 %ls
20 """.splitlines(keepends=True), """\
20 """.splitlines(keepends=True), (2, 4), """\
21 21 for a in range(5):
22 22 get_ipython().run_line_magic('ls', '')
23 23 """.splitlines(keepends=True))
24 24
25 25 MULTILINE_MAGIC_ASSIGN = ("""\
26 26 a = f()
27 27 b = %foo \\
28 28 bar
29 29 g()
30 """.splitlines(keepends=True), """\
30 """.splitlines(keepends=True), (2, 4), """\
31 31 a = f()
32 32 b = get_ipython().run_line_magic('foo', ' bar')
33 33 g()
34 34 """.splitlines(keepends=True))
35 35
36 36 MULTILINE_SYSTEM_ASSIGN = ("""\
37 37 a = f()
38 38 b = !foo \\
39 39 bar
40 40 g()
41 """.splitlines(keepends=True), """\
41 """.splitlines(keepends=True), (2, 4), """\
42 42 a = f()
43 43 b = get_ipython().getoutput('foo bar')
44 44 g()
45 45 """.splitlines(keepends=True))
46 46
47 47 AUTOCALL_QUOTE = (
48 [",f 1 2 3\n"],
48 [",f 1 2 3\n"], (1, 0),
49 49 ['f("1", "2", "3")\n']
50 50 )
51 51
52 52 AUTOCALL_QUOTE2 = (
53 [";f 1 2 3\n"],
53 [";f 1 2 3\n"], (1, 0),
54 54 ['f("1 2 3")\n']
55 55 )
56 56
57 57 AUTOCALL_PAREN = (
58 ["/f 1 2 3\n"],
58 ["/f 1 2 3\n"], (1, 0),
59 59 ['f(1, 2, 3)\n']
60 60 )
61 61
62 SIMPLE_HELP = (
63 ["foo?\n"], (1, 0),
64 ["get_ipython().run_line_magic('pinfo', 'foo')\n"]
65 )
66
67 DETAILED_HELP = (
68 ["foo??\n"], (1, 0),
69 ["get_ipython().run_line_magic('pinfo2', 'foo')\n"]
70 )
71
72 MAGIC_HELP = (
73 ["%foo?\n"], (1, 0),
74 ["get_ipython().run_line_magic('pinfo', '%foo')\n"]
75 )
76
77 HELP_IN_EXPR = (
78 ["a = b + c?\n"], (1, 0),
79 ["get_ipython().set_next_input('a = b + c');"
80 "get_ipython().run_line_magic('pinfo', 'c')\n"]
81 )
82
83 HELP_CONTINUED_LINE = ("""\
84 a = \\
85 zip?
86 """.splitlines(keepends=True), (1, 0),
87 [r"get_ipython().set_next_input('a = \\\nzip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
88 )
89
90 HELP_MULTILINE = ("""\
91 (a,
92 b) = zip?
93 """.splitlines(keepends=True), (1, 0),
94 [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
95 )
96
97 def check_find(transformer, case, match=True):
98 sample, expected_start, _ = case
99 tbl = make_tokens_by_line(sample)
100 res = transformer.find(tbl)
101 if match:
102 # start_line is stored 0-indexed, expected values are 1-indexed
103 nt.assert_equal((res.start_line+1, res.start_col), expected_start)
104 return res
105 else:
106 nt.assert_is(res, None)
107
108 def check_transform(transformer_cls, case):
109 lines, start, expected = case
110 transformer = transformer_cls(start)
111 nt.assert_equal(transformer.transform(lines), expected)
112
62 113 def test_continued_line():
63 114 lines = MULTILINE_MAGIC_ASSIGN[0]
64 115 nt.assert_equal(ipt2.find_end_of_continued_line(lines, 1), 2)
65 116
66 117 nt.assert_equal(ipt2.assemble_continued_line(lines, (1, 5), 2), "foo bar")
67 118
68 119 def test_find_assign_magic():
69 tbl = make_tokens_by_line(MULTILINE_MAGIC_ASSIGN[0])
70 nt.assert_equal(ipt2.MagicAssign.find(tbl), (2, 4))
71
72 tbl = make_tokens_by_line(MULTILINE_SYSTEM_ASSIGN[0]) # Nothing to find
73 nt.assert_equal(ipt2.MagicAssign.find(tbl), None)
120 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
121 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
74 122
75 123 def test_transform_assign_magic():
76 res = ipt2.MagicAssign.transform(MULTILINE_MAGIC_ASSIGN[0], (2, 4))
77 nt.assert_equal(res, MULTILINE_MAGIC_ASSIGN[1])
124 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
78 125
79 126 def test_find_assign_system():
80 tbl = make_tokens_by_line(MULTILINE_SYSTEM_ASSIGN[0])
81 nt.assert_equal(ipt2.SystemAssign.find(tbl), (2, 4))
127 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
128 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
129 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
130 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
82 131
83 tbl = make_tokens_by_line(["a = !ls\n"])
84 nt.assert_equal(ipt2.SystemAssign.find(tbl), (1, 5))
132 def test_transform_assign_system():
133 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
85 134
86 tbl = make_tokens_by_line(["a=!ls\n"])
87 nt.assert_equal(ipt2.SystemAssign.find(tbl), (1, 2))
135 def test_find_magic_escape():
136 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
137 check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
138 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
88 139
89 tbl = make_tokens_by_line(MULTILINE_MAGIC_ASSIGN[0]) # Nothing to find
90 nt.assert_equal(ipt2.SystemAssign.find(tbl), None)
140 def test_transform_magic_escape():
141 check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
142 check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
91 143
92 def test_transform_assign_system():
93 res = ipt2.SystemAssign.transform(MULTILINE_SYSTEM_ASSIGN[0], (2, 4))
94 nt.assert_equal(res, MULTILINE_SYSTEM_ASSIGN[1])
144 def test_find_autocalls():
145 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
146 print("Testing %r" % case[0])
147 check_find(ipt2.EscapedCommand, case)
95 148
96 def test_find_magic_escape():
97 tbl = make_tokens_by_line(MULTILINE_MAGIC[0])
98 nt.assert_equal(ipt2.EscapedCommand.find(tbl), (2, 0))
149 def test_transform_autocall():
150 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
151 print("Testing %r" % case[0])
152 check_transform(ipt2.EscapedCommand, case)
99 153
100 tbl = make_tokens_by_line(INDENTED_MAGIC[0])
101 nt.assert_equal(ipt2.EscapedCommand.find(tbl), (2, 4))
154 def test_find_help():
155 for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
156 check_find(ipt2.HelpEnd, case)
102 157
103 tbl = make_tokens_by_line(MULTILINE_MAGIC_ASSIGN[0]) # Shouldn't find a = %foo
104 nt.assert_equal(ipt2.EscapedCommand.find(tbl), None)
158 tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE)
159 nt.assert_equal(tf.q_line, 1)
160 nt.assert_equal(tf.q_col, 3)
105 161
106 def test_transform_magic_escape():
107 res = ipt2.EscapedCommand.transform(MULTILINE_MAGIC[0], (2, 0))
108 nt.assert_equal(res, MULTILINE_MAGIC[1])
162 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE)
163 nt.assert_equal(tf.q_line, 1)
164 nt.assert_equal(tf.q_col, 8)
109 165
110 res = ipt2.EscapedCommand.transform(INDENTED_MAGIC[0], (2, 4))
111 nt.assert_equal(res, INDENTED_MAGIC[1])
166 # ? in a comment does not trigger help
167 check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False)
168 # Nor in a string
169 check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
112 170
113 def test_find_autocalls():
114 for sample, _ in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
115 print("Testing %r" % sample)
116 tbl = make_tokens_by_line(sample)
117 nt.assert_equal(ipt2.EscapedCommand.find(tbl), (1, 0))
171 def test_transform_help():
172 tf = ipt2.HelpEnd((1, 0), (1, 9))
173 nt.assert_equal(tf.transform(HELP_IN_EXPR[0]), HELP_IN_EXPR[2])
118 174
119 def test_transform_autocall():
120 for sample, expected in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
121 print("Testing %r" % sample)
122 res = ipt2.EscapedCommand.transform(sample, (1, 0))
123 nt.assert_equal(res, expected)
175 tf = ipt2.HelpEnd((1, 0), (2, 3))
176 nt.assert_equal(tf.transform(HELP_CONTINUED_LINE[0]), HELP_CONTINUED_LINE[2])
177
178 tf = ipt2.HelpEnd((1, 0), (2, 8))
179 nt.assert_equal(tf.transform(HELP_MULTILINE[0]), HELP_MULTILINE[2])
General Comments 0
You need to be logged in to leave comments. Login now