##// END OF EJS Templates
Deprecate inputtransformer since 7.0...
M Bussonnier -
Show More
@@ -1,544 +1,577
1 """DEPRECATED: Input transformer classes to support IPython special syntax.
1 """DEPRECATED: Input transformer classes to support IPython special syntax.
2
2
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
4
4
5 This includes the machinery to recognise and transform ``%magic`` commands,
5 This includes the machinery to recognise and transform ``%magic`` commands,
6 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
6 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
7 """
7 """
8 import abc
8 import abc
9 import functools
9 import functools
10 import re
10 import re
11 import tokenize
11 import tokenize
12 import warnings
12 from tokenize import untokenize, TokenError
13 from tokenize import untokenize, TokenError
13 from io import StringIO
14 from io import StringIO
14
15
15 from IPython.core.splitinput import LineInfo
16 from IPython.core.splitinput import LineInfo
16 from IPython.utils import tokenutil
17 from IPython.utils import tokenutil
17
18
18 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
19 # Globals
20 # Globals
20 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
21
22
22 # The escape sequences that define the syntax transformations IPython will
23 # The escape sequences that define the syntax transformations IPython will
23 # apply to user input. These can NOT be just changed here: many regular
24 # apply to user input. These can NOT be just changed here: many regular
24 # expressions and other parts of the code may use their hardcoded values, and
25 # expressions and other parts of the code may use their hardcoded values, and
25 # for all intents and purposes they constitute the 'IPython syntax', so they
26 # for all intents and purposes they constitute the 'IPython syntax', so they
26 # should be considered fixed.
27 # should be considered fixed.
27
28
28 ESC_SHELL = '!' # Send line to underlying system shell
29 ESC_SHELL = '!' # Send line to underlying system shell
29 ESC_SH_CAP = '!!' # Send line to system shell and capture output
30 ESC_SH_CAP = '!!' # Send line to system shell and capture output
30 ESC_HELP = '?' # Find information about object
31 ESC_HELP = '?' # Find information about object
31 ESC_HELP2 = '??' # Find extra-detailed information about object
32 ESC_HELP2 = '??' # Find extra-detailed information about object
32 ESC_MAGIC = '%' # Call magic function
33 ESC_MAGIC = '%' # Call magic function
33 ESC_MAGIC2 = '%%' # Call cell-magic function
34 ESC_MAGIC2 = '%%' # Call cell-magic function
34 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
35 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
35 ESC_QUOTE2 = ';' # Quote all args as a single string, call
36 ESC_QUOTE2 = ';' # Quote all args as a single string, call
36 ESC_PAREN = '/' # Call first argument with rest of line as arguments
37 ESC_PAREN = '/' # Call first argument with rest of line as arguments
37
38
38 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
39 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
39 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
40 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
40 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
41 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
41
42
42
43
43 class InputTransformer(metaclass=abc.ABCMeta):
44 class InputTransformer(metaclass=abc.ABCMeta):
44 """Abstract base class for line-based input transformers."""
45 """Abstract base class for line-based input transformers."""
45
46
47 def __init__(self):
48 warnings.warn(
49 "`InputTransformer` has been deprecated since IPython 7.0"
50 " and emit a warnig since IPython 8.31, it"
51 " will be removed in the future",
52 DeprecationWarning,
53 stacklevel=2,
54 )
55
46 @abc.abstractmethod
56 @abc.abstractmethod
47 def push(self, line):
57 def push(self, line):
48 """Send a line of input to the transformer, returning the transformed
58 """Send a line of input to the transformer, returning the transformed
49 input or None if the transformer is waiting for more input.
59 input or None if the transformer is waiting for more input.
50
60
51 Must be overridden by subclasses.
61 Must be overridden by subclasses.
52
62
53 Implementations may raise ``SyntaxError`` if the input is invalid. No
63 Implementations may raise ``SyntaxError`` if the input is invalid. No
54 other exceptions may be raised.
64 other exceptions may be raised.
55 """
65 """
56 pass
66 pass
57
67
58 @abc.abstractmethod
68 @abc.abstractmethod
59 def reset(self):
69 def reset(self):
60 """Return, transformed any lines that the transformer has accumulated,
70 """Return, transformed any lines that the transformer has accumulated,
61 and reset its internal state.
71 and reset its internal state.
62
72
63 Must be overridden by subclasses.
73 Must be overridden by subclasses.
64 """
74 """
65 pass
75 pass
66
76
67 @classmethod
77 @classmethod
68 def wrap(cls, func):
78 def wrap(cls, func):
69 """Can be used by subclasses as a decorator, to return a factory that
79 """Can be used by subclasses as a decorator, to return a factory that
70 will allow instantiation with the decorated object.
80 will allow instantiation with the decorated object.
71 """
81 """
72 @functools.wraps(func)
82 @functools.wraps(func)
73 def transformer_factory(**kwargs):
83 def transformer_factory(**kwargs):
74 return cls(func, **kwargs) # type: ignore [call-arg]
84 return cls(func, **kwargs) # type: ignore [call-arg]
75
85
76 return transformer_factory
86 return transformer_factory
77
87
78 class StatelessInputTransformer(InputTransformer):
88 class StatelessInputTransformer(InputTransformer):
79 """Wrapper for a stateless input transformer implemented as a function."""
89 """Wrapper for a stateless input transformer implemented as a function."""
80 def __init__(self, func):
90 def __init__(self, func):
91 super().__init__()
92 warnings.warn(
93 "`StatelessInputTransformer` has been deprecated since IPython 7.0"
94 " and emit a warnig since IPython 8.31, it"
95 " will be removed in the future",
96 DeprecationWarning,
97 stacklevel=2,
98 )
81 self.func = func
99 self.func = func
82
100
83 def __repr__(self):
101 def __repr__(self):
84 return "StatelessInputTransformer(func={0!r})".format(self.func)
102 return "StatelessInputTransformer(func={0!r})".format(self.func)
85
103
86 def push(self, line):
104 def push(self, line):
87 """Send a line of input to the transformer, returning the
105 """Send a line of input to the transformer, returning the
88 transformed input."""
106 transformed input."""
89 return self.func(line)
107 return self.func(line)
90
108
91 def reset(self):
109 def reset(self):
92 """No-op - exists for compatibility."""
110 """No-op - exists for compatibility."""
93 pass
111 pass
94
112
95 class CoroutineInputTransformer(InputTransformer):
113 class CoroutineInputTransformer(InputTransformer):
96 """Wrapper for an input transformer implemented as a coroutine."""
114 """Wrapper for an input transformer implemented as a coroutine."""
97 def __init__(self, coro, **kwargs):
115 def __init__(self, coro, **kwargs):
98 # Prime it
116 # Prime it
117 super().__init__()
118 warnings.warn(
119 "`CoroutineInputTransformer` has been deprecated since IPython 7.0"
120 " and emit a warnig since IPython 8.31, it"
121 " will be removed in the future",
122 DeprecationWarning,
123 stacklevel=2,
124 )
99 self.coro = coro(**kwargs)
125 self.coro = coro(**kwargs)
100 next(self.coro)
126 next(self.coro)
101
127
102 def __repr__(self):
128 def __repr__(self):
103 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
129 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
104
130
105 def push(self, line):
131 def push(self, line):
106 """Send a line of input to the transformer, returning the
132 """Send a line of input to the transformer, returning the
107 transformed input or None if the transformer is waiting for more
133 transformed input or None if the transformer is waiting for more
108 input.
134 input.
109 """
135 """
110 return self.coro.send(line)
136 return self.coro.send(line)
111
137
112 def reset(self):
138 def reset(self):
113 """Return, transformed any lines that the transformer has
139 """Return, transformed any lines that the transformer has
114 accumulated, and reset its internal state.
140 accumulated, and reset its internal state.
115 """
141 """
116 return self.coro.send(None)
142 return self.coro.send(None)
117
143
118 class TokenInputTransformer(InputTransformer):
144 class TokenInputTransformer(InputTransformer):
119 """Wrapper for a token-based input transformer.
145 """Wrapper for a token-based input transformer.
120
146
121 func should accept a list of tokens (5-tuples, see tokenize docs), and
147 func should accept a list of tokens (5-tuples, see tokenize docs), and
122 return an iterable which can be passed to tokenize.untokenize().
148 return an iterable which can be passed to tokenize.untokenize().
123 """
149 """
124 def __init__(self, func):
150 def __init__(self, func):
151 warnings.warn(
152 "`CoroutineInputTransformer` has been deprecated since IPython 7.0"
153 " and emit a warnig since IPython 8.31, it"
154 " will be removed in the future",
155 DeprecationWarning,
156 stacklevel=2,
157 )
125 self.func = func
158 self.func = func
126 self.buf = []
159 self.buf = []
127 self.reset_tokenizer()
160 self.reset_tokenizer()
128
161
129 def reset_tokenizer(self):
162 def reset_tokenizer(self):
130 it = iter(self.buf)
163 it = iter(self.buf)
131 self.tokenizer = tokenutil.generate_tokens_catch_errors(it.__next__)
164 self.tokenizer = tokenutil.generate_tokens_catch_errors(it.__next__)
132
165
133 def push(self, line):
166 def push(self, line):
134 self.buf.append(line + '\n')
167 self.buf.append(line + '\n')
135 if all(l.isspace() for l in self.buf):
168 if all(l.isspace() for l in self.buf):
136 return self.reset()
169 return self.reset()
137
170
138 tokens = []
171 tokens = []
139 stop_at_NL = False
172 stop_at_NL = False
140 try:
173 try:
141 for intok in self.tokenizer:
174 for intok in self.tokenizer:
142 tokens.append(intok)
175 tokens.append(intok)
143 t = intok[0]
176 t = intok[0]
144 if t == tokenize.NEWLINE or (stop_at_NL and t == tokenize.NL):
177 if t == tokenize.NEWLINE or (stop_at_NL and t == tokenize.NL):
145 # Stop before we try to pull a line we don't have yet
178 # Stop before we try to pull a line we don't have yet
146 break
179 break
147 elif t == tokenize.ERRORTOKEN:
180 elif t == tokenize.ERRORTOKEN:
148 stop_at_NL = True
181 stop_at_NL = True
149 except TokenError:
182 except TokenError:
150 # Multi-line statement - stop and try again with the next line
183 # Multi-line statement - stop and try again with the next line
151 self.reset_tokenizer()
184 self.reset_tokenizer()
152 return None
185 return None
153
186
154 return self.output(tokens)
187 return self.output(tokens)
155
188
156 def output(self, tokens):
189 def output(self, tokens):
157 self.buf.clear()
190 self.buf.clear()
158 self.reset_tokenizer()
191 self.reset_tokenizer()
159 return untokenize(self.func(tokens)).rstrip('\n')
192 return untokenize(self.func(tokens)).rstrip('\n')
160
193
161 def reset(self):
194 def reset(self):
162 l = ''.join(self.buf)
195 l = ''.join(self.buf)
163 self.buf.clear()
196 self.buf.clear()
164 self.reset_tokenizer()
197 self.reset_tokenizer()
165 if l:
198 if l:
166 return l.rstrip('\n')
199 return l.rstrip('\n')
167
200
168 class assemble_python_lines(TokenInputTransformer):
201 class assemble_python_lines(TokenInputTransformer):
169 def __init__(self):
202 def __init__(self):
170 super(assemble_python_lines, self).__init__(None)
203 super().__init__(None)
171
204
172 def output(self, tokens):
205 def output(self, tokens):
173 return self.reset()
206 return self.reset()
174
207
175 @CoroutineInputTransformer.wrap
208 @CoroutineInputTransformer.wrap
176 def assemble_logical_lines():
209 def assemble_logical_lines():
177 r"""Join lines following explicit line continuations (\)"""
210 r"""Join lines following explicit line continuations (\)"""
178 line = ''
211 line = ''
179 while True:
212 while True:
180 line = (yield line)
213 line = (yield line)
181 if not line or line.isspace():
214 if not line or line.isspace():
182 continue
215 continue
183
216
184 parts = []
217 parts = []
185 while line is not None:
218 while line is not None:
186 if line.endswith('\\') and (not has_comment(line)):
219 if line.endswith('\\') and (not has_comment(line)):
187 parts.append(line[:-1])
220 parts.append(line[:-1])
188 line = (yield None) # Get another line
221 line = (yield None) # Get another line
189 else:
222 else:
190 parts.append(line)
223 parts.append(line)
191 break
224 break
192
225
193 # Output
226 # Output
194 line = ''.join(parts)
227 line = ''.join(parts)
195
228
196 # Utilities
229 # Utilities
197 def _make_help_call(target: str, esc: str, lspace: str) -> str:
230 def _make_help_call(target: str, esc: str, lspace: str) -> str:
198 """Prepares a pinfo(2)/psearch call from a target name and the escape
231 """Prepares a pinfo(2)/psearch call from a target name and the escape
199 (i.e. ? or ??)"""
232 (i.e. ? or ??)"""
200 method = 'pinfo2' if esc == '??' \
233 method = 'pinfo2' if esc == '??' \
201 else 'psearch' if '*' in target \
234 else 'psearch' if '*' in target \
202 else 'pinfo'
235 else 'pinfo'
203 arg = " ".join([method, target])
236 arg = " ".join([method, target])
204 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
237 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
205 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
238 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
206 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
239 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
207 return "%sget_ipython().run_line_magic(%r, %r)" % (
240 return "%sget_ipython().run_line_magic(%r, %r)" % (
208 lspace,
241 lspace,
209 t_magic_name,
242 t_magic_name,
210 t_magic_arg_s,
243 t_magic_arg_s,
211 )
244 )
212
245
213
246
214 # These define the transformations for the different escape characters.
247 # These define the transformations for the different escape characters.
215 def _tr_system(line_info: LineInfo):
248 def _tr_system(line_info: LineInfo):
216 "Translate lines escaped with: !"
249 "Translate lines escaped with: !"
217 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
250 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
218 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
251 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
219
252
220
253
221 def _tr_system2(line_info: LineInfo):
254 def _tr_system2(line_info: LineInfo):
222 "Translate lines escaped with: !!"
255 "Translate lines escaped with: !!"
223 cmd = line_info.line.lstrip()[2:]
256 cmd = line_info.line.lstrip()[2:]
224 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
257 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
225
258
226
259
227 def _tr_help(line_info: LineInfo):
260 def _tr_help(line_info: LineInfo):
228 "Translate lines escaped with: ?/??"
261 "Translate lines escaped with: ?/??"
229 # A naked help line should just fire the intro help screen
262 # A naked help line should just fire the intro help screen
230 if not line_info.line[1:]:
263 if not line_info.line[1:]:
231 return 'get_ipython().show_usage()'
264 return 'get_ipython().show_usage()'
232
265
233 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
266 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
234
267
235
268
236 def _tr_magic(line_info: LineInfo):
269 def _tr_magic(line_info: LineInfo):
237 "Translate lines escaped with: %"
270 "Translate lines escaped with: %"
238 tpl = '%sget_ipython().run_line_magic(%r, %r)'
271 tpl = '%sget_ipython().run_line_magic(%r, %r)'
239 if line_info.line.startswith(ESC_MAGIC2):
272 if line_info.line.startswith(ESC_MAGIC2):
240 return line_info.line
273 return line_info.line
241 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
274 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
242 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
275 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
243 t_magic_name, _, t_magic_arg_s = cmd.partition(' ')
276 t_magic_name, _, t_magic_arg_s = cmd.partition(' ')
244 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
277 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
245 return tpl % (line_info.pre, t_magic_name, t_magic_arg_s)
278 return tpl % (line_info.pre, t_magic_name, t_magic_arg_s)
246
279
247
280
248 def _tr_quote(line_info: LineInfo):
281 def _tr_quote(line_info: LineInfo):
249 "Translate lines escaped with: ,"
282 "Translate lines escaped with: ,"
250 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
283 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
251 '", "'.join(line_info.the_rest.split()) )
284 '", "'.join(line_info.the_rest.split()) )
252
285
253
286
254 def _tr_quote2(line_info: LineInfo):
287 def _tr_quote2(line_info: LineInfo):
255 "Translate lines escaped with: ;"
288 "Translate lines escaped with: ;"
256 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
289 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
257 line_info.the_rest)
290 line_info.the_rest)
258
291
259
292
260 def _tr_paren(line_info: LineInfo):
293 def _tr_paren(line_info: LineInfo):
261 "Translate lines escaped with: /"
294 "Translate lines escaped with: /"
262 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
295 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
263 ", ".join(line_info.the_rest.split()))
296 ", ".join(line_info.the_rest.split()))
264
297
265 tr = { ESC_SHELL : _tr_system,
298 tr = { ESC_SHELL : _tr_system,
266 ESC_SH_CAP : _tr_system2,
299 ESC_SH_CAP : _tr_system2,
267 ESC_HELP : _tr_help,
300 ESC_HELP : _tr_help,
268 ESC_HELP2 : _tr_help,
301 ESC_HELP2 : _tr_help,
269 ESC_MAGIC : _tr_magic,
302 ESC_MAGIC : _tr_magic,
270 ESC_QUOTE : _tr_quote,
303 ESC_QUOTE : _tr_quote,
271 ESC_QUOTE2 : _tr_quote2,
304 ESC_QUOTE2 : _tr_quote2,
272 ESC_PAREN : _tr_paren }
305 ESC_PAREN : _tr_paren }
273
306
274 @StatelessInputTransformer.wrap
307 @StatelessInputTransformer.wrap
275 def escaped_commands(line: str):
308 def escaped_commands(line: str):
276 """Transform escaped commands - %magic, !system, ?help + various autocalls."""
309 """Transform escaped commands - %magic, !system, ?help + various autocalls."""
277 if not line or line.isspace():
310 if not line or line.isspace():
278 return line
311 return line
279 lineinf = LineInfo(line)
312 lineinf = LineInfo(line)
280 if lineinf.esc not in tr:
313 if lineinf.esc not in tr:
281 return line
314 return line
282
315
283 return tr[lineinf.esc](lineinf)
316 return tr[lineinf.esc](lineinf)
284
317
285 _initial_space_re = re.compile(r'\s*')
318 _initial_space_re = re.compile(r'\s*')
286
319
287 _help_end_re = re.compile(r"""(%{0,2}
320 _help_end_re = re.compile(r"""(%{0,2}
288 (?!\d)[\w*]+ # Variable name
321 (?!\d)[\w*]+ # Variable name
289 (\.(?!\d)[\w*]+)* # .etc.etc
322 (\.(?!\d)[\w*]+)* # .etc.etc
290 )
323 )
291 (\?\??)$ # ? or ??
324 (\?\??)$ # ? or ??
292 """,
325 """,
293 re.VERBOSE)
326 re.VERBOSE)
294
327
295 # Extra pseudotokens for multiline strings and data structures
328 # Extra pseudotokens for multiline strings and data structures
296 _MULTILINE_STRING = object()
329 _MULTILINE_STRING = object()
297 _MULTILINE_STRUCTURE = object()
330 _MULTILINE_STRUCTURE = object()
298
331
299 def _line_tokens(line):
332 def _line_tokens(line):
300 """Helper for has_comment and ends_in_comment_or_string."""
333 """Helper for has_comment and ends_in_comment_or_string."""
301 readline = StringIO(line).readline
334 readline = StringIO(line).readline
302 toktypes = set()
335 toktypes = set()
303 try:
336 try:
304 for t in tokenutil.generate_tokens_catch_errors(readline):
337 for t in tokenutil.generate_tokens_catch_errors(readline):
305 toktypes.add(t[0])
338 toktypes.add(t[0])
306 except TokenError as e:
339 except TokenError as e:
307 # There are only two cases where a TokenError is raised.
340 # There are only two cases where a TokenError is raised.
308 if 'multi-line string' in e.args[0]:
341 if 'multi-line string' in e.args[0]:
309 toktypes.add(_MULTILINE_STRING)
342 toktypes.add(_MULTILINE_STRING)
310 else:
343 else:
311 toktypes.add(_MULTILINE_STRUCTURE)
344 toktypes.add(_MULTILINE_STRUCTURE)
312 return toktypes
345 return toktypes
313
346
314 def has_comment(src):
347 def has_comment(src):
315 """Indicate whether an input line has (i.e. ends in, or is) a comment.
348 """Indicate whether an input line has (i.e. ends in, or is) a comment.
316
349
317 This uses tokenize, so it can distinguish comments from # inside strings.
350 This uses tokenize, so it can distinguish comments from # inside strings.
318
351
319 Parameters
352 Parameters
320 ----------
353 ----------
321 src : string
354 src : string
322 A single line input string.
355 A single line input string.
323
356
324 Returns
357 Returns
325 -------
358 -------
326 comment : bool
359 comment : bool
327 True if source has a comment.
360 True if source has a comment.
328 """
361 """
329 return (tokenize.COMMENT in _line_tokens(src))
362 return (tokenize.COMMENT in _line_tokens(src))
330
363
331 def ends_in_comment_or_string(src):
364 def ends_in_comment_or_string(src):
332 """Indicates whether or not an input line ends in a comment or within
365 """Indicates whether or not an input line ends in a comment or within
333 a multiline string.
366 a multiline string.
334
367
335 Parameters
368 Parameters
336 ----------
369 ----------
337 src : string
370 src : string
338 A single line input string.
371 A single line input string.
339
372
340 Returns
373 Returns
341 -------
374 -------
342 comment : bool
375 comment : bool
343 True if source ends in a comment or multiline string.
376 True if source ends in a comment or multiline string.
344 """
377 """
345 toktypes = _line_tokens(src)
378 toktypes = _line_tokens(src)
346 return (tokenize.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
379 return (tokenize.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
347
380
348
381
349 @StatelessInputTransformer.wrap
382 @StatelessInputTransformer.wrap
350 def help_end(line: str):
383 def help_end(line: str):
351 """Translate lines with ?/?? at the end"""
384 """Translate lines with ?/?? at the end"""
352 m = _help_end_re.search(line)
385 m = _help_end_re.search(line)
353 if m is None or ends_in_comment_or_string(line):
386 if m is None or ends_in_comment_or_string(line):
354 return line
387 return line
355 target = m.group(1)
388 target = m.group(1)
356 esc = m.group(3)
389 esc = m.group(3)
357 match = _initial_space_re.match(line)
390 match = _initial_space_re.match(line)
358 assert match is not None
391 assert match is not None
359 lspace = match.group(0)
392 lspace = match.group(0)
360
393
361 return _make_help_call(target, esc, lspace)
394 return _make_help_call(target, esc, lspace)
362
395
363
396
364 @CoroutineInputTransformer.wrap
397 @CoroutineInputTransformer.wrap
365 def cellmagic(end_on_blank_line: bool = False):
398 def cellmagic(end_on_blank_line: bool = False):
366 """Captures & transforms cell magics.
399 """Captures & transforms cell magics.
367
400
368 After a cell magic is started, this stores up any lines it gets until it is
401 After a cell magic is started, this stores up any lines it gets until it is
369 reset (sent None).
402 reset (sent None).
370 """
403 """
371 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
404 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
372 cellmagic_help_re = re.compile(r'%%\w+\?')
405 cellmagic_help_re = re.compile(r'%%\w+\?')
373 line = ''
406 line = ''
374 while True:
407 while True:
375 line = (yield line)
408 line = (yield line)
376 # consume leading empty lines
409 # consume leading empty lines
377 while not line:
410 while not line:
378 line = (yield line)
411 line = (yield line)
379
412
380 if not line.startswith(ESC_MAGIC2):
413 if not line.startswith(ESC_MAGIC2):
381 # This isn't a cell magic, idle waiting for reset then start over
414 # This isn't a cell magic, idle waiting for reset then start over
382 while line is not None:
415 while line is not None:
383 line = (yield line)
416 line = (yield line)
384 continue
417 continue
385
418
386 if cellmagic_help_re.match(line):
419 if cellmagic_help_re.match(line):
387 # This case will be handled by help_end
420 # This case will be handled by help_end
388 continue
421 continue
389
422
390 first = line
423 first = line
391 body = []
424 body = []
392 line = (yield None)
425 line = (yield None)
393 while (line is not None) and \
426 while (line is not None) and \
394 ((line.strip() != '') or not end_on_blank_line):
427 ((line.strip() != '') or not end_on_blank_line):
395 body.append(line)
428 body.append(line)
396 line = (yield None)
429 line = (yield None)
397
430
398 # Output
431 # Output
399 magic_name, _, first = first.partition(' ')
432 magic_name, _, first = first.partition(' ')
400 magic_name = magic_name.lstrip(ESC_MAGIC2)
433 magic_name = magic_name.lstrip(ESC_MAGIC2)
401 line = tpl % (magic_name, first, u'\n'.join(body))
434 line = tpl % (magic_name, first, u'\n'.join(body))
402
435
403
436
404 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
437 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
405 """Remove matching input prompts from a block of input.
438 """Remove matching input prompts from a block of input.
406
439
407 Parameters
440 Parameters
408 ----------
441 ----------
409 prompt_re : regular expression
442 prompt_re : regular expression
410 A regular expression matching any input prompt (including continuation)
443 A regular expression matching any input prompt (including continuation)
411 initial_re : regular expression, optional
444 initial_re : regular expression, optional
412 A regular expression matching only the initial prompt, but not continuation.
445 A regular expression matching only the initial prompt, but not continuation.
413 If no initial expression is given, prompt_re will be used everywhere.
446 If no initial expression is given, prompt_re will be used everywhere.
414 Used mainly for plain Python prompts, where the continuation prompt
447 Used mainly for plain Python prompts, where the continuation prompt
415 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
448 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
416
449
417 Notes
450 Notes
418 -----
451 -----
419 If `initial_re` and `prompt_re differ`,
452 If `initial_re` and `prompt_re differ`,
420 only `initial_re` will be tested against the first line.
453 only `initial_re` will be tested against the first line.
421 If any prompt is found on the first two lines,
454 If any prompt is found on the first two lines,
422 prompts will be stripped from the rest of the block.
455 prompts will be stripped from the rest of the block.
423 """
456 """
424 if initial_re is None:
457 if initial_re is None:
425 initial_re = prompt_re
458 initial_re = prompt_re
426 line = ''
459 line = ''
427 while True:
460 while True:
428 line = (yield line)
461 line = (yield line)
429
462
430 # First line of cell
463 # First line of cell
431 if line is None:
464 if line is None:
432 continue
465 continue
433 out, n1 = initial_re.subn('', line, count=1)
466 out, n1 = initial_re.subn('', line, count=1)
434 if turnoff_re and not n1:
467 if turnoff_re and not n1:
435 if turnoff_re.match(line):
468 if turnoff_re.match(line):
436 # We're in e.g. a cell magic; disable this transformer for
469 # We're in e.g. a cell magic; disable this transformer for
437 # the rest of the cell.
470 # the rest of the cell.
438 while line is not None:
471 while line is not None:
439 line = (yield line)
472 line = (yield line)
440 continue
473 continue
441
474
442 line = (yield out)
475 line = (yield out)
443
476
444 if line is None:
477 if line is None:
445 continue
478 continue
446 # check for any prompt on the second line of the cell,
479 # check for any prompt on the second line of the cell,
447 # because people often copy from just after the first prompt,
480 # because people often copy from just after the first prompt,
448 # so we might not see it in the first line.
481 # so we might not see it in the first line.
449 out, n2 = prompt_re.subn('', line, count=1)
482 out, n2 = prompt_re.subn('', line, count=1)
450 line = (yield out)
483 line = (yield out)
451
484
452 if n1 or n2:
485 if n1 or n2:
453 # Found a prompt in the first two lines - check for it in
486 # Found a prompt in the first two lines - check for it in
454 # the rest of the cell as well.
487 # the rest of the cell as well.
455 while line is not None:
488 while line is not None:
456 line = (yield prompt_re.sub('', line, count=1))
489 line = (yield prompt_re.sub('', line, count=1))
457
490
458 else:
491 else:
459 # Prompts not in input - wait for reset
492 # Prompts not in input - wait for reset
460 while line is not None:
493 while line is not None:
461 line = (yield line)
494 line = (yield line)
462
495
463 @CoroutineInputTransformer.wrap
496 @CoroutineInputTransformer.wrap
464 def classic_prompt():
497 def classic_prompt():
465 """Strip the >>>/... prompts of the Python interactive shell."""
498 """Strip the >>>/... prompts of the Python interactive shell."""
466 # FIXME: non-capturing version (?:...) usable?
499 # FIXME: non-capturing version (?:...) usable?
467 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
500 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
468 initial_re = re.compile(r'^>>>( |$)')
501 initial_re = re.compile(r'^>>>( |$)')
469 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
502 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
470 turnoff_re = re.compile(r'^[%!]')
503 turnoff_re = re.compile(r'^[%!]')
471 return _strip_prompts(prompt_re, initial_re, turnoff_re)
504 return _strip_prompts(prompt_re, initial_re, turnoff_re)
472
505
473 @CoroutineInputTransformer.wrap
506 @CoroutineInputTransformer.wrap
474 def ipy_prompt():
507 def ipy_prompt():
475 """Strip IPython's In [1]:/...: prompts."""
508 """Strip IPython's In [1]:/...: prompts."""
476 # FIXME: non-capturing version (?:...) usable?
509 # FIXME: non-capturing version (?:...) usable?
477 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
510 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
478 # Disable prompt stripping inside cell magics
511 # Disable prompt stripping inside cell magics
479 turnoff_re = re.compile(r'^%%')
512 turnoff_re = re.compile(r'^%%')
480 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
513 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
481
514
482
515
483 @CoroutineInputTransformer.wrap
516 @CoroutineInputTransformer.wrap
484 def leading_indent():
517 def leading_indent():
485 """Remove leading indentation.
518 """Remove leading indentation.
486
519
487 If the first line starts with a spaces or tabs, the same whitespace will be
520 If the first line starts with a spaces or tabs, the same whitespace will be
488 removed from each following line until it is reset.
521 removed from each following line until it is reset.
489 """
522 """
490 space_re = re.compile(r'^[ \t]+')
523 space_re = re.compile(r'^[ \t]+')
491 line = ''
524 line = ''
492 while True:
525 while True:
493 line = (yield line)
526 line = (yield line)
494
527
495 if line is None:
528 if line is None:
496 continue
529 continue
497
530
498 m = space_re.match(line)
531 m = space_re.match(line)
499 if m:
532 if m:
500 space = m.group(0)
533 space = m.group(0)
501 while line is not None:
534 while line is not None:
502 if line.startswith(space):
535 if line.startswith(space):
503 line = line[len(space):]
536 line = line[len(space):]
504 line = (yield line)
537 line = (yield line)
505 else:
538 else:
506 # No leading spaces - wait for reset
539 # No leading spaces - wait for reset
507 while line is not None:
540 while line is not None:
508 line = (yield line)
541 line = (yield line)
509
542
510
543
511 _assign_pat = \
544 _assign_pat = \
512 r'''(?P<lhs>(\s*)
545 r'''(?P<lhs>(\s*)
513 ([\w\.]+) # Initial identifier
546 ([\w\.]+) # Initial identifier
514 (\s*,\s*
547 (\s*,\s*
515 \*?[\w\.]+)* # Further identifiers for unpacking
548 \*?[\w\.]+)* # Further identifiers for unpacking
516 \s*?,? # Trailing comma
549 \s*?,? # Trailing comma
517 )
550 )
518 \s*=\s*
551 \s*=\s*
519 '''
552 '''
520
553
521 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
554 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
522 assign_system_template = '%s = get_ipython().getoutput(%r)'
555 assign_system_template = '%s = get_ipython().getoutput(%r)'
523 @StatelessInputTransformer.wrap
556 @StatelessInputTransformer.wrap
524 def assign_from_system(line):
557 def assign_from_system(line):
525 """Transform assignment from system commands (e.g. files = !ls)"""
558 """Transform assignment from system commands (e.g. files = !ls)"""
526 m = assign_system_re.match(line)
559 m = assign_system_re.match(line)
527 if m is None:
560 if m is None:
528 return line
561 return line
529
562
530 return assign_system_template % m.group('lhs', 'cmd')
563 return assign_system_template % m.group('lhs', 'cmd')
531
564
532 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
565 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
533 assign_magic_template = '%s = get_ipython().run_line_magic(%r, %r)'
566 assign_magic_template = '%s = get_ipython().run_line_magic(%r, %r)'
534 @StatelessInputTransformer.wrap
567 @StatelessInputTransformer.wrap
535 def assign_from_magic(line):
568 def assign_from_magic(line):
536 """Transform assignment from magic commands (e.g. a = %who_ls)"""
569 """Transform assignment from magic commands (e.g. a = %who_ls)"""
537 m = assign_magic_re.match(line)
570 m = assign_magic_re.match(line)
538 if m is None:
571 if m is None:
539 return line
572 return line
540 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
573 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
541 m_lhs, m_cmd = m.group('lhs', 'cmd')
574 m_lhs, m_cmd = m.group('lhs', 'cmd')
542 t_magic_name, _, t_magic_arg_s = m_cmd.partition(' ')
575 t_magic_name, _, t_magic_arg_s = m_cmd.partition(' ')
543 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
576 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
544 return assign_magic_template % (m_lhs, t_magic_name, t_magic_arg_s)
577 return assign_magic_template % (m_lhs, t_magic_name, t_magic_arg_s)
@@ -1,643 +1,637
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the inputsplitter module."""
2 """Tests for the inputsplitter module."""
3
3
4
4
5 # Copyright (c) IPython Development Team.
5 # Copyright (c) IPython Development Team.
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7
7
8 import unittest
8 import unittest
9 import pytest
9 import pytest
10 import sys
10 import sys
11
11
12 with pytest.warns(DeprecationWarning, match="inputsplitter"):
12 with pytest.warns(DeprecationWarning, match="inputsplitter"):
13 from IPython.core import inputsplitter as isp
13 from IPython.core import inputsplitter as isp
14 from IPython.core.inputtransformer import InputTransformer
14 from IPython.core.inputtransformer import InputTransformer
15 from IPython.core.tests.test_inputtransformer import syntax, syntax_ml
15 from IPython.core.tests.test_inputtransformer import syntax, syntax_ml
16 from IPython.testing import tools as tt
16 from IPython.testing import tools as tt
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Semi-complete examples (also used as tests)
19 # Semi-complete examples (also used as tests)
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 # Note: at the bottom, there's a slightly more complete version of this that
22 # Note: at the bottom, there's a slightly more complete version of this that
23 # can be useful during development of code here.
23 # can be useful during development of code here.
24
24
25 def mini_interactive_loop(input_func):
25 def mini_interactive_loop(input_func):
26 """Minimal example of the logic of an interactive interpreter loop.
26 """Minimal example of the logic of an interactive interpreter loop.
27
27
28 This serves as an example, and it is used by the test system with a fake
28 This serves as an example, and it is used by the test system with a fake
29 raw_input that simulates interactive input."""
29 raw_input that simulates interactive input."""
30
30
31 from IPython.core.inputsplitter import InputSplitter
31 from IPython.core.inputsplitter import InputSplitter
32
32
33 isp = InputSplitter()
33 isp = InputSplitter()
34 # In practice, this input loop would be wrapped in an outside loop to read
34 # In practice, this input loop would be wrapped in an outside loop to read
35 # input indefinitely, until some exit/quit command was issued. Here we
35 # input indefinitely, until some exit/quit command was issued. Here we
36 # only illustrate the basic inner loop.
36 # only illustrate the basic inner loop.
37 while isp.push_accepts_more():
37 while isp.push_accepts_more():
38 indent = ' '*isp.get_indent_spaces()
38 indent = ' '*isp.get_indent_spaces()
39 prompt = '>>> ' + indent
39 prompt = '>>> ' + indent
40 line = indent + input_func(prompt)
40 line = indent + input_func(prompt)
41 isp.push(line)
41 isp.push(line)
42
42
43 # Here we just return input so we can use it in a test suite, but a real
43 # Here we just return input so we can use it in a test suite, but a real
44 # interpreter would instead send it for execution somewhere.
44 # interpreter would instead send it for execution somewhere.
45 src = isp.source_reset()
45 src = isp.source_reset()
46 # print('Input source was:\n', src) # dbg
46 # print('Input source was:\n', src) # dbg
47 return src
47 return src
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Test utilities, just for local use
50 # Test utilities, just for local use
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53
53
54 def pseudo_input(lines):
54 def pseudo_input(lines):
55 """Return a function that acts like raw_input but feeds the input list."""
55 """Return a function that acts like raw_input but feeds the input list."""
56 ilines = iter(lines)
56 ilines = iter(lines)
57 def raw_in(prompt):
57 def raw_in(prompt):
58 try:
58 try:
59 return next(ilines)
59 return next(ilines)
60 except StopIteration:
60 except StopIteration:
61 return ''
61 return ''
62 return raw_in
62 return raw_in
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Tests
65 # Tests
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67 def test_spaces():
67 def test_spaces():
68 tests = [('', 0),
68 tests = [('', 0),
69 (' ', 1),
69 (' ', 1),
70 ('\n', 0),
70 ('\n', 0),
71 (' \n', 1),
71 (' \n', 1),
72 ('x', 0),
72 ('x', 0),
73 (' x', 1),
73 (' x', 1),
74 (' x',2),
74 (' x',2),
75 (' x',4),
75 (' x',4),
76 # Note: tabs are counted as a single whitespace!
76 # Note: tabs are counted as a single whitespace!
77 ('\tx', 1),
77 ('\tx', 1),
78 ('\t x', 2),
78 ('\t x', 2),
79 ]
79 ]
80 with pytest.warns(PendingDeprecationWarning):
80 with pytest.warns(PendingDeprecationWarning):
81 tt.check_pairs(isp.num_ini_spaces, tests)
81 tt.check_pairs(isp.num_ini_spaces, tests)
82
82
83
83
84 def test_remove_comments():
84 def test_remove_comments():
85 tests = [('text', 'text'),
85 tests = [('text', 'text'),
86 ('text # comment', 'text '),
86 ('text # comment', 'text '),
87 ('text # comment\n', 'text \n'),
87 ('text # comment\n', 'text \n'),
88 ('text # comment \n', 'text \n'),
88 ('text # comment \n', 'text \n'),
89 ('line # c \nline\n','line \nline\n'),
89 ('line # c \nline\n','line \nline\n'),
90 ('line # c \nline#c2 \nline\nline #c\n\n',
90 ('line # c \nline#c2 \nline\nline #c\n\n',
91 'line \nline\nline\nline \n\n'),
91 'line \nline\nline\nline \n\n'),
92 ]
92 ]
93 tt.check_pairs(isp.remove_comments, tests)
93 tt.check_pairs(isp.remove_comments, tests)
94
94
95
95
96 def test_get_input_encoding():
96 def test_get_input_encoding():
97 encoding = isp.get_input_encoding()
97 encoding = isp.get_input_encoding()
98 assert isinstance(encoding, str)
98 assert isinstance(encoding, str)
99 # simple-minded check that at least encoding a simple string works with the
99 # simple-minded check that at least encoding a simple string works with the
100 # encoding we got.
100 # encoding we got.
101 assert "test".encode(encoding) == b"test"
101 assert "test".encode(encoding) == b"test"
102
102
103
103
104 class NoInputEncodingTestCase(unittest.TestCase):
104 class NoInputEncodingTestCase(unittest.TestCase):
105 def setUp(self):
105 def setUp(self):
106 self.old_stdin = sys.stdin
106 self.old_stdin = sys.stdin
107 class X: pass
107 class X: pass
108 fake_stdin = X()
108 fake_stdin = X()
109 sys.stdin = fake_stdin
109 sys.stdin = fake_stdin
110
110
111 def test(self):
111 def test(self):
112 # Verify that if sys.stdin has no 'encoding' attribute we do the right
112 # Verify that if sys.stdin has no 'encoding' attribute we do the right
113 # thing
113 # thing
114 enc = isp.get_input_encoding()
114 enc = isp.get_input_encoding()
115 self.assertEqual(enc, 'ascii')
115 self.assertEqual(enc, 'ascii')
116
116
117 def tearDown(self):
117 def tearDown(self):
118 sys.stdin = self.old_stdin
118 sys.stdin = self.old_stdin
119
119
120
120
121 class InputSplitterTestCase(unittest.TestCase):
121 class InputSplitterTestCase(unittest.TestCase):
122 def setUp(self):
122 def setUp(self):
123 self.isp = isp.InputSplitter()
123 self.isp = isp.InputSplitter()
124
124
125 def test_reset(self):
125 def test_reset(self):
126 isp = self.isp
126 isp = self.isp
127 isp.push('x=1')
127 isp.push('x=1')
128 isp.reset()
128 isp.reset()
129 self.assertEqual(isp._buffer, [])
129 self.assertEqual(isp._buffer, [])
130 self.assertEqual(isp.get_indent_spaces(), 0)
130 self.assertEqual(isp.get_indent_spaces(), 0)
131 self.assertEqual(isp.source, '')
131 self.assertEqual(isp.source, '')
132 self.assertEqual(isp.code, None)
132 self.assertEqual(isp.code, None)
133 self.assertEqual(isp._is_complete, False)
133 self.assertEqual(isp._is_complete, False)
134
134
135 def test_source(self):
135 def test_source(self):
136 self.isp._store('1')
136 self.isp._store('1')
137 self.isp._store('2')
137 self.isp._store('2')
138 self.assertEqual(self.isp.source, '1\n2\n')
138 self.assertEqual(self.isp.source, '1\n2\n')
139 self.assertEqual(len(self.isp._buffer)>0, True)
139 self.assertEqual(len(self.isp._buffer)>0, True)
140 self.assertEqual(self.isp.source_reset(), '1\n2\n')
140 self.assertEqual(self.isp.source_reset(), '1\n2\n')
141 self.assertEqual(self.isp._buffer, [])
141 self.assertEqual(self.isp._buffer, [])
142 self.assertEqual(self.isp.source, '')
142 self.assertEqual(self.isp.source, '')
143
143
144 def test_indent(self):
144 def test_indent(self):
145 isp = self.isp # shorthand
145 isp = self.isp # shorthand
146 isp.push('x=1')
146 isp.push('x=1')
147 self.assertEqual(isp.get_indent_spaces(), 0)
147 self.assertEqual(isp.get_indent_spaces(), 0)
148 isp.push('if 1:\n x=1')
148 isp.push('if 1:\n x=1')
149 self.assertEqual(isp.get_indent_spaces(), 4)
149 self.assertEqual(isp.get_indent_spaces(), 4)
150 isp.push('y=2\n')
150 isp.push('y=2\n')
151 self.assertEqual(isp.get_indent_spaces(), 0)
151 self.assertEqual(isp.get_indent_spaces(), 0)
152
152
153 def test_indent2(self):
153 def test_indent2(self):
154 isp = self.isp
154 isp = self.isp
155 isp.push('if 1:')
155 isp.push('if 1:')
156 self.assertEqual(isp.get_indent_spaces(), 4)
156 self.assertEqual(isp.get_indent_spaces(), 4)
157 isp.push(' x=1')
157 isp.push(' x=1')
158 self.assertEqual(isp.get_indent_spaces(), 4)
158 self.assertEqual(isp.get_indent_spaces(), 4)
159 # Blank lines shouldn't change the indent level
159 # Blank lines shouldn't change the indent level
160 isp.push(' '*2)
160 isp.push(' '*2)
161 self.assertEqual(isp.get_indent_spaces(), 4)
161 self.assertEqual(isp.get_indent_spaces(), 4)
162
162
163 def test_indent3(self):
163 def test_indent3(self):
164 isp = self.isp
164 isp = self.isp
165 # When a multiline statement contains parens or multiline strings, we
165 # When a multiline statement contains parens or multiline strings, we
166 # shouldn't get confused.
166 # shouldn't get confused.
167 isp.push("if 1:")
167 isp.push("if 1:")
168 isp.push(" x = (1+\n 2)")
168 isp.push(" x = (1+\n 2)")
169 self.assertEqual(isp.get_indent_spaces(), 4)
169 self.assertEqual(isp.get_indent_spaces(), 4)
170
170
171 def test_indent4(self):
171 def test_indent4(self):
172 isp = self.isp
172 isp = self.isp
173 # whitespace after ':' should not screw up indent level
173 # whitespace after ':' should not screw up indent level
174 isp.push('if 1: \n x=1')
174 isp.push('if 1: \n x=1')
175 self.assertEqual(isp.get_indent_spaces(), 4)
175 self.assertEqual(isp.get_indent_spaces(), 4)
176 isp.push('y=2\n')
176 isp.push('y=2\n')
177 self.assertEqual(isp.get_indent_spaces(), 0)
177 self.assertEqual(isp.get_indent_spaces(), 0)
178 isp.push('if 1:\t\n x=1')
178 isp.push('if 1:\t\n x=1')
179 self.assertEqual(isp.get_indent_spaces(), 4)
179 self.assertEqual(isp.get_indent_spaces(), 4)
180 isp.push('y=2\n')
180 isp.push('y=2\n')
181 self.assertEqual(isp.get_indent_spaces(), 0)
181 self.assertEqual(isp.get_indent_spaces(), 0)
182
182
183 def test_dedent_pass(self):
183 def test_dedent_pass(self):
184 isp = self.isp # shorthand
184 isp = self.isp # shorthand
185 # should NOT cause dedent
185 # should NOT cause dedent
186 isp.push('if 1:\n passes = 5')
186 isp.push('if 1:\n passes = 5')
187 self.assertEqual(isp.get_indent_spaces(), 4)
187 self.assertEqual(isp.get_indent_spaces(), 4)
188 isp.push('if 1:\n pass')
188 isp.push('if 1:\n pass')
189 self.assertEqual(isp.get_indent_spaces(), 0)
189 self.assertEqual(isp.get_indent_spaces(), 0)
190 isp.push('if 1:\n pass ')
190 isp.push('if 1:\n pass ')
191 self.assertEqual(isp.get_indent_spaces(), 0)
191 self.assertEqual(isp.get_indent_spaces(), 0)
192
192
193 def test_dedent_break(self):
193 def test_dedent_break(self):
194 isp = self.isp # shorthand
194 isp = self.isp # shorthand
195 # should NOT cause dedent
195 # should NOT cause dedent
196 isp.push('while 1:\n breaks = 5')
196 isp.push('while 1:\n breaks = 5')
197 self.assertEqual(isp.get_indent_spaces(), 4)
197 self.assertEqual(isp.get_indent_spaces(), 4)
198 isp.push('while 1:\n break')
198 isp.push('while 1:\n break')
199 self.assertEqual(isp.get_indent_spaces(), 0)
199 self.assertEqual(isp.get_indent_spaces(), 0)
200 isp.push('while 1:\n break ')
200 isp.push('while 1:\n break ')
201 self.assertEqual(isp.get_indent_spaces(), 0)
201 self.assertEqual(isp.get_indent_spaces(), 0)
202
202
203 def test_dedent_continue(self):
203 def test_dedent_continue(self):
204 isp = self.isp # shorthand
204 isp = self.isp # shorthand
205 # should NOT cause dedent
205 # should NOT cause dedent
206 isp.push('while 1:\n continues = 5')
206 isp.push('while 1:\n continues = 5')
207 self.assertEqual(isp.get_indent_spaces(), 4)
207 self.assertEqual(isp.get_indent_spaces(), 4)
208 isp.push('while 1:\n continue')
208 isp.push('while 1:\n continue')
209 self.assertEqual(isp.get_indent_spaces(), 0)
209 self.assertEqual(isp.get_indent_spaces(), 0)
210 isp.push('while 1:\n continue ')
210 isp.push('while 1:\n continue ')
211 self.assertEqual(isp.get_indent_spaces(), 0)
211 self.assertEqual(isp.get_indent_spaces(), 0)
212
212
213 def test_dedent_raise(self):
213 def test_dedent_raise(self):
214 isp = self.isp # shorthand
214 isp = self.isp # shorthand
215 # should NOT cause dedent
215 # should NOT cause dedent
216 isp.push('if 1:\n raised = 4')
216 isp.push('if 1:\n raised = 4')
217 self.assertEqual(isp.get_indent_spaces(), 4)
217 self.assertEqual(isp.get_indent_spaces(), 4)
218 isp.push('if 1:\n raise TypeError()')
218 isp.push('if 1:\n raise TypeError()')
219 self.assertEqual(isp.get_indent_spaces(), 0)
219 self.assertEqual(isp.get_indent_spaces(), 0)
220 isp.push('if 1:\n raise')
220 isp.push('if 1:\n raise')
221 self.assertEqual(isp.get_indent_spaces(), 0)
221 self.assertEqual(isp.get_indent_spaces(), 0)
222 isp.push('if 1:\n raise ')
222 isp.push('if 1:\n raise ')
223 self.assertEqual(isp.get_indent_spaces(), 0)
223 self.assertEqual(isp.get_indent_spaces(), 0)
224
224
225 def test_dedent_return(self):
225 def test_dedent_return(self):
226 isp = self.isp # shorthand
226 isp = self.isp # shorthand
227 # should NOT cause dedent
227 # should NOT cause dedent
228 isp.push('if 1:\n returning = 4')
228 isp.push('if 1:\n returning = 4')
229 self.assertEqual(isp.get_indent_spaces(), 4)
229 self.assertEqual(isp.get_indent_spaces(), 4)
230 isp.push('if 1:\n return 5 + 493')
230 isp.push('if 1:\n return 5 + 493')
231 self.assertEqual(isp.get_indent_spaces(), 0)
231 self.assertEqual(isp.get_indent_spaces(), 0)
232 isp.push('if 1:\n return')
232 isp.push('if 1:\n return')
233 self.assertEqual(isp.get_indent_spaces(), 0)
233 self.assertEqual(isp.get_indent_spaces(), 0)
234 isp.push('if 1:\n return ')
234 isp.push('if 1:\n return ')
235 self.assertEqual(isp.get_indent_spaces(), 0)
235 self.assertEqual(isp.get_indent_spaces(), 0)
236 isp.push('if 1:\n return(0)')
236 isp.push('if 1:\n return(0)')
237 self.assertEqual(isp.get_indent_spaces(), 0)
237 self.assertEqual(isp.get_indent_spaces(), 0)
238
238
239 def test_push(self):
239 def test_push(self):
240 isp = self.isp
240 isp = self.isp
241 self.assertEqual(isp.push('x=1'), True)
241 self.assertEqual(isp.push('x=1'), True)
242
242
243 def test_push2(self):
243 def test_push2(self):
244 isp = self.isp
244 isp = self.isp
245 self.assertEqual(isp.push('if 1:'), False)
245 self.assertEqual(isp.push('if 1:'), False)
246 for line in [' x=1', '# a comment', ' y=2']:
246 for line in [' x=1', '# a comment', ' y=2']:
247 print(line)
247 print(line)
248 self.assertEqual(isp.push(line), True)
248 self.assertEqual(isp.push(line), True)
249
249
250 def test_push3(self):
250 def test_push3(self):
251 isp = self.isp
251 isp = self.isp
252 isp.push('if True:')
252 isp.push('if True:')
253 isp.push(' a = 1')
253 isp.push(' a = 1')
254 self.assertEqual(isp.push('b = [1,'), False)
254 self.assertEqual(isp.push('b = [1,'), False)
255
255
256 def test_push_accepts_more(self):
256 def test_push_accepts_more(self):
257 isp = self.isp
257 isp = self.isp
258 isp.push('x=1')
258 isp.push('x=1')
259 self.assertEqual(isp.push_accepts_more(), False)
259 self.assertEqual(isp.push_accepts_more(), False)
260
260
261 def test_push_accepts_more2(self):
261 def test_push_accepts_more2(self):
262 isp = self.isp
262 isp = self.isp
263 isp.push('if 1:')
263 isp.push('if 1:')
264 self.assertEqual(isp.push_accepts_more(), True)
264 self.assertEqual(isp.push_accepts_more(), True)
265 isp.push(' x=1')
265 isp.push(' x=1')
266 self.assertEqual(isp.push_accepts_more(), True)
266 self.assertEqual(isp.push_accepts_more(), True)
267 isp.push('')
267 isp.push('')
268 self.assertEqual(isp.push_accepts_more(), False)
268 self.assertEqual(isp.push_accepts_more(), False)
269
269
270 def test_push_accepts_more3(self):
270 def test_push_accepts_more3(self):
271 isp = self.isp
271 isp = self.isp
272 isp.push("x = (2+\n3)")
272 isp.push("x = (2+\n3)")
273 self.assertEqual(isp.push_accepts_more(), False)
273 self.assertEqual(isp.push_accepts_more(), False)
274
274
275 def test_push_accepts_more4(self):
275 def test_push_accepts_more4(self):
276 isp = self.isp
276 isp = self.isp
277 # When a multiline statement contains parens or multiline strings, we
277 # When a multiline statement contains parens or multiline strings, we
278 # shouldn't get confused.
278 # shouldn't get confused.
279 # FIXME: we should be able to better handle de-dents in statements like
279 # FIXME: we should be able to better handle de-dents in statements like
280 # multiline strings and multiline expressions (continued with \ or
280 # multiline strings and multiline expressions (continued with \ or
281 # parens). Right now we aren't handling the indentation tracking quite
281 # parens). Right now we aren't handling the indentation tracking quite
282 # correctly with this, though in practice it may not be too much of a
282 # correctly with this, though in practice it may not be too much of a
283 # problem. We'll need to see.
283 # problem. We'll need to see.
284 isp.push("if 1:")
284 isp.push("if 1:")
285 isp.push(" x = (2+")
285 isp.push(" x = (2+")
286 isp.push(" 3)")
286 isp.push(" 3)")
287 self.assertEqual(isp.push_accepts_more(), True)
287 self.assertEqual(isp.push_accepts_more(), True)
288 isp.push(" y = 3")
288 isp.push(" y = 3")
289 self.assertEqual(isp.push_accepts_more(), True)
289 self.assertEqual(isp.push_accepts_more(), True)
290 isp.push('')
290 isp.push('')
291 self.assertEqual(isp.push_accepts_more(), False)
291 self.assertEqual(isp.push_accepts_more(), False)
292
292
293 def test_push_accepts_more5(self):
293 def test_push_accepts_more5(self):
294 isp = self.isp
294 isp = self.isp
295 isp.push('try:')
295 isp.push('try:')
296 isp.push(' a = 5')
296 isp.push(' a = 5')
297 isp.push('except:')
297 isp.push('except:')
298 isp.push(' raise')
298 isp.push(' raise')
299 # We want to be able to add an else: block at this point, so it should
299 # We want to be able to add an else: block at this point, so it should
300 # wait for a blank line.
300 # wait for a blank line.
301 self.assertEqual(isp.push_accepts_more(), True)
301 self.assertEqual(isp.push_accepts_more(), True)
302
302
303 def test_continuation(self):
303 def test_continuation(self):
304 isp = self.isp
304 isp = self.isp
305 isp.push("import os, \\")
305 isp.push("import os, \\")
306 self.assertEqual(isp.push_accepts_more(), True)
306 self.assertEqual(isp.push_accepts_more(), True)
307 isp.push("sys")
307 isp.push("sys")
308 self.assertEqual(isp.push_accepts_more(), False)
308 self.assertEqual(isp.push_accepts_more(), False)
309
309
310 def test_syntax_error(self):
310 def test_syntax_error(self):
311 isp = self.isp
311 isp = self.isp
312 # Syntax errors immediately produce a 'ready' block, so the invalid
312 # Syntax errors immediately produce a 'ready' block, so the invalid
313 # Python can be sent to the kernel for evaluation with possible ipython
313 # Python can be sent to the kernel for evaluation with possible ipython
314 # special-syntax conversion.
314 # special-syntax conversion.
315 isp.push('run foo')
315 isp.push('run foo')
316 self.assertEqual(isp.push_accepts_more(), False)
316 self.assertEqual(isp.push_accepts_more(), False)
317
317
318 def test_unicode(self):
318 def test_unicode(self):
319 self.isp.push(u"Pérez")
319 self.isp.push(u"Pérez")
320 self.isp.push(u'\xc3\xa9')
320 self.isp.push(u'\xc3\xa9')
321 self.isp.push(u"u'\xc3\xa9'")
321 self.isp.push(u"u'\xc3\xa9'")
322
322
323 @pytest.mark.xfail(
324 reason="Bug in python 3.9.8 – bpo 45738",
325 condition=sys.version_info in [(3, 11, 0, "alpha", 2)],
326 raises=SystemError,
327 strict=True,
328 )
329 def test_line_continuation(self):
323 def test_line_continuation(self):
330 """ Test issue #2108."""
324 """ Test issue #2108."""
331 isp = self.isp
325 isp = self.isp
332 # A blank line after a line continuation should not accept more
326 # A blank line after a line continuation should not accept more
333 isp.push("1 \\\n\n")
327 isp.push("1 \\\n\n")
334 self.assertEqual(isp.push_accepts_more(), False)
328 self.assertEqual(isp.push_accepts_more(), False)
335 # Whitespace after a \ is a SyntaxError. The only way to test that
329 # Whitespace after a \ is a SyntaxError. The only way to test that
336 # here is to test that push doesn't accept more (as with
330 # here is to test that push doesn't accept more (as with
337 # test_syntax_error() above).
331 # test_syntax_error() above).
338 isp.push(r"1 \ ")
332 isp.push(r"1 \ ")
339 self.assertEqual(isp.push_accepts_more(), False)
333 self.assertEqual(isp.push_accepts_more(), False)
340 # Even if the line is continuable (c.f. the regular Python
334 # Even if the line is continuable (c.f. the regular Python
341 # interpreter)
335 # interpreter)
342 isp.push(r"(1 \ ")
336 isp.push(r"(1 \ ")
343 self.assertEqual(isp.push_accepts_more(), False)
337 self.assertEqual(isp.push_accepts_more(), False)
344
338
345 def test_check_complete(self):
339 def test_check_complete(self):
346 isp = self.isp
340 isp = self.isp
347 self.assertEqual(isp.check_complete("a = 1"), ('complete', None))
341 self.assertEqual(isp.check_complete("a = 1"), ('complete', None))
348 self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4))
342 self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4))
349 self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None))
343 self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None))
350 self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0))
344 self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0))
351 self.assertEqual(isp.check_complete("def a():\n x=1\n global x"), ('invalid', None))
345 self.assertEqual(isp.check_complete("def a():\n x=1\n global x"), ('invalid', None))
352
346
353 class InteractiveLoopTestCase(unittest.TestCase):
347 class InteractiveLoopTestCase(unittest.TestCase):
354 """Tests for an interactive loop like a python shell.
348 """Tests for an interactive loop like a python shell.
355 """
349 """
356 def check_ns(self, lines, ns):
350 def check_ns(self, lines, ns):
357 """Validate that the given input lines produce the resulting namespace.
351 """Validate that the given input lines produce the resulting namespace.
358
352
359 Note: the input lines are given exactly as they would be typed in an
353 Note: the input lines are given exactly as they would be typed in an
360 auto-indenting environment, as mini_interactive_loop above already does
354 auto-indenting environment, as mini_interactive_loop above already does
361 auto-indenting and prepends spaces to the input.
355 auto-indenting and prepends spaces to the input.
362 """
356 """
363 src = mini_interactive_loop(pseudo_input(lines))
357 src = mini_interactive_loop(pseudo_input(lines))
364 test_ns = {}
358 test_ns = {}
365 exec(src, test_ns)
359 exec(src, test_ns)
366 # We can't check that the provided ns is identical to the test_ns,
360 # We can't check that the provided ns is identical to the test_ns,
367 # because Python fills test_ns with extra keys (copyright, etc). But
361 # because Python fills test_ns with extra keys (copyright, etc). But
368 # we can check that the given dict is *contained* in test_ns
362 # we can check that the given dict is *contained* in test_ns
369 for k,v in ns.items():
363 for k,v in ns.items():
370 self.assertEqual(test_ns[k], v)
364 self.assertEqual(test_ns[k], v)
371
365
372 def test_simple(self):
366 def test_simple(self):
373 self.check_ns(['x=1'], dict(x=1))
367 self.check_ns(['x=1'], dict(x=1))
374
368
375 def test_simple2(self):
369 def test_simple2(self):
376 self.check_ns(['if 1:', 'x=2'], dict(x=2))
370 self.check_ns(['if 1:', 'x=2'], dict(x=2))
377
371
378 def test_xy(self):
372 def test_xy(self):
379 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
373 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
380
374
381 def test_abc(self):
375 def test_abc(self):
382 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
376 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
383
377
384 def test_multi(self):
378 def test_multi(self):
385 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
379 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
386
380
387
381
388 class IPythonInputTestCase(InputSplitterTestCase):
382 class IPythonInputTestCase(InputSplitterTestCase):
389 """By just creating a new class whose .isp is a different instance, we
383 """By just creating a new class whose .isp is a different instance, we
390 re-run the same test battery on the new input splitter.
384 re-run the same test battery on the new input splitter.
391
385
392 In addition, this runs the tests over the syntax and syntax_ml dicts that
386 In addition, this runs the tests over the syntax and syntax_ml dicts that
393 were tested by individual functions, as part of the OO interface.
387 were tested by individual functions, as part of the OO interface.
394
388
395 It also makes some checks on the raw buffer storage.
389 It also makes some checks on the raw buffer storage.
396 """
390 """
397
391
398 def setUp(self):
392 def setUp(self):
399 self.isp = isp.IPythonInputSplitter()
393 self.isp = isp.IPythonInputSplitter()
400
394
401 def test_syntax(self):
395 def test_syntax(self):
402 """Call all single-line syntax tests from the main object"""
396 """Call all single-line syntax tests from the main object"""
403 isp = self.isp
397 isp = self.isp
404 for example in syntax.values():
398 for example in syntax.values():
405 for raw, out_t in example:
399 for raw, out_t in example:
406 if raw.startswith(' '):
400 if raw.startswith(' '):
407 continue
401 continue
408
402
409 isp.push(raw+'\n')
403 isp.push(raw+'\n')
410 out_raw = isp.source_raw
404 out_raw = isp.source_raw
411 out = isp.source_reset()
405 out = isp.source_reset()
412 self.assertEqual(out.rstrip(), out_t,
406 self.assertEqual(out.rstrip(), out_t,
413 tt.pair_fail_msg.format("inputsplitter",raw, out_t, out))
407 tt.pair_fail_msg.format("inputsplitter",raw, out_t, out))
414 self.assertEqual(out_raw.rstrip(), raw.rstrip())
408 self.assertEqual(out_raw.rstrip(), raw.rstrip())
415
409
416 def test_syntax_multiline(self):
410 def test_syntax_multiline(self):
417 isp = self.isp
411 isp = self.isp
418 for example in syntax_ml.values():
412 for example in syntax_ml.values():
419 for line_pairs in example:
413 for line_pairs in example:
420 out_t_parts = []
414 out_t_parts = []
421 raw_parts = []
415 raw_parts = []
422 for lraw, out_t_part in line_pairs:
416 for lraw, out_t_part in line_pairs:
423 if out_t_part is not None:
417 if out_t_part is not None:
424 out_t_parts.append(out_t_part)
418 out_t_parts.append(out_t_part)
425
419
426 if lraw is not None:
420 if lraw is not None:
427 isp.push(lraw)
421 isp.push(lraw)
428 raw_parts.append(lraw)
422 raw_parts.append(lraw)
429
423
430 out_raw = isp.source_raw
424 out_raw = isp.source_raw
431 out = isp.source_reset()
425 out = isp.source_reset()
432 out_t = '\n'.join(out_t_parts).rstrip()
426 out_t = '\n'.join(out_t_parts).rstrip()
433 raw = '\n'.join(raw_parts).rstrip()
427 raw = '\n'.join(raw_parts).rstrip()
434 self.assertEqual(out.rstrip(), out_t)
428 self.assertEqual(out.rstrip(), out_t)
435 self.assertEqual(out_raw.rstrip(), raw)
429 self.assertEqual(out_raw.rstrip(), raw)
436
430
437 def test_syntax_multiline_cell(self):
431 def test_syntax_multiline_cell(self):
438 isp = self.isp
432 isp = self.isp
439 for example in syntax_ml.values():
433 for example in syntax_ml.values():
440
434
441 out_t_parts = []
435 out_t_parts = []
442 for line_pairs in example:
436 for line_pairs in example:
443 raw = '\n'.join(r for r, _ in line_pairs if r is not None)
437 raw = '\n'.join(r for r, _ in line_pairs if r is not None)
444 out_t = '\n'.join(t for _,t in line_pairs if t is not None)
438 out_t = '\n'.join(t for _,t in line_pairs if t is not None)
445 out = isp.transform_cell(raw)
439 out = isp.transform_cell(raw)
446 # Match ignoring trailing whitespace
440 # Match ignoring trailing whitespace
447 self.assertEqual(out.rstrip(), out_t.rstrip())
441 self.assertEqual(out.rstrip(), out_t.rstrip())
448
442
449 def test_cellmagic_preempt(self):
443 def test_cellmagic_preempt(self):
450 isp = self.isp
444 isp = self.isp
451 for raw, name, line, cell in [
445 for raw, name, line, cell in [
452 ("%%cellm a\nIn[1]:", u'cellm', u'a', u'In[1]:'),
446 ("%%cellm a\nIn[1]:", u'cellm', u'a', u'In[1]:'),
453 ("%%cellm \nline\n>>> hi", u'cellm', u'', u'line\n>>> hi'),
447 ("%%cellm \nline\n>>> hi", u'cellm', u'', u'line\n>>> hi'),
454 (">>> %%cellm \nline\n>>> hi", u'cellm', u'', u'line\nhi'),
448 (">>> %%cellm \nline\n>>> hi", u'cellm', u'', u'line\nhi'),
455 ("%%cellm \n>>> hi", u'cellm', u'', u'>>> hi'),
449 ("%%cellm \n>>> hi", u'cellm', u'', u'>>> hi'),
456 ("%%cellm \nline1\nline2", u'cellm', u'', u'line1\nline2'),
450 ("%%cellm \nline1\nline2", u'cellm', u'', u'line1\nline2'),
457 ("%%cellm \nline1\\\\\nline2", u'cellm', u'', u'line1\\\\\nline2'),
451 ("%%cellm \nline1\\\\\nline2", u'cellm', u'', u'line1\\\\\nline2'),
458 ]:
452 ]:
459 expected = "get_ipython().run_cell_magic(%r, %r, %r)" % (
453 expected = "get_ipython().run_cell_magic(%r, %r, %r)" % (
460 name, line, cell
454 name, line, cell
461 )
455 )
462 out = isp.transform_cell(raw)
456 out = isp.transform_cell(raw)
463 self.assertEqual(out.rstrip(), expected.rstrip())
457 self.assertEqual(out.rstrip(), expected.rstrip())
464
458
465 def test_multiline_passthrough(self):
459 def test_multiline_passthrough(self):
466 isp = self.isp
460 isp = self.isp
467 class CommentTransformer(InputTransformer):
461 class CommentTransformer(InputTransformer):
468 def __init__(self):
462 def __init__(self):
469 self._lines = []
463 self._lines = []
470
464
471 def push(self, line):
465 def push(self, line):
472 self._lines.append(line + '#')
466 self._lines.append(line + '#')
473
467
474 def reset(self):
468 def reset(self):
475 text = '\n'.join(self._lines)
469 text = '\n'.join(self._lines)
476 self._lines = []
470 self._lines = []
477 return text
471 return text
478
472
479 isp.physical_line_transforms.insert(0, CommentTransformer())
473 isp.physical_line_transforms.insert(0, CommentTransformer())
480
474
481 for raw, expected in [
475 for raw, expected in [
482 ("a=5", "a=5#"),
476 ("a=5", "a=5#"),
483 ("%ls foo", "get_ipython().run_line_magic(%r, %r)" % (u'ls', u'foo#')),
477 ("%ls foo", "get_ipython().run_line_magic(%r, %r)" % (u'ls', u'foo#')),
484 ("!ls foo\n%ls bar", "get_ipython().system(%r)\nget_ipython().run_line_magic(%r, %r)" % (
478 ("!ls foo\n%ls bar", "get_ipython().system(%r)\nget_ipython().run_line_magic(%r, %r)" % (
485 u'ls foo#', u'ls', u'bar#'
479 u'ls foo#', u'ls', u'bar#'
486 )),
480 )),
487 ("1\n2\n3\n%ls foo\n4\n5", "1#\n2#\n3#\nget_ipython().run_line_magic(%r, %r)\n4#\n5#" % (u'ls', u'foo#')),
481 ("1\n2\n3\n%ls foo\n4\n5", "1#\n2#\n3#\nget_ipython().run_line_magic(%r, %r)\n4#\n5#" % (u'ls', u'foo#')),
488 ]:
482 ]:
489 out = isp.transform_cell(raw)
483 out = isp.transform_cell(raw)
490 self.assertEqual(out.rstrip(), expected.rstrip())
484 self.assertEqual(out.rstrip(), expected.rstrip())
491
485
492 #-----------------------------------------------------------------------------
486 #-----------------------------------------------------------------------------
493 # Main - use as a script, mostly for developer experiments
487 # Main - use as a script, mostly for developer experiments
494 #-----------------------------------------------------------------------------
488 #-----------------------------------------------------------------------------
495
489
496 if __name__ == '__main__':
490 if __name__ == '__main__':
497 # A simple demo for interactive experimentation. This code will not get
491 # A simple demo for interactive experimentation. This code will not get
498 # picked up by any test suite.
492 # picked up by any test suite.
499 from IPython.core.inputsplitter import IPythonInputSplitter
493 from IPython.core.inputsplitter import IPythonInputSplitter
500
494
501 # configure here the syntax to use, prompt and whether to autoindent
495 # configure here the syntax to use, prompt and whether to autoindent
502 #isp, start_prompt = InputSplitter(), '>>> '
496 #isp, start_prompt = InputSplitter(), '>>> '
503 isp, start_prompt = IPythonInputSplitter(), 'In> '
497 isp, start_prompt = IPythonInputSplitter(), 'In> '
504
498
505 autoindent = True
499 autoindent = True
506 #autoindent = False
500 #autoindent = False
507
501
508 try:
502 try:
509 while True:
503 while True:
510 prompt = start_prompt
504 prompt = start_prompt
511 while isp.push_accepts_more():
505 while isp.push_accepts_more():
512 indent = ' '*isp.get_indent_spaces()
506 indent = ' '*isp.get_indent_spaces()
513 if autoindent:
507 if autoindent:
514 line = indent + input(prompt+indent)
508 line = indent + input(prompt+indent)
515 else:
509 else:
516 line = input(prompt)
510 line = input(prompt)
517 isp.push(line)
511 isp.push(line)
518 prompt = '... '
512 prompt = '... '
519
513
520 # Here we just return input so we can use it in a test suite, but a
514 # Here we just return input so we can use it in a test suite, but a
521 # real interpreter would instead send it for execution somewhere.
515 # real interpreter would instead send it for execution somewhere.
522 #src = isp.source; raise EOFError # dbg
516 #src = isp.source; raise EOFError # dbg
523 raw = isp.source_raw
517 raw = isp.source_raw
524 src = isp.source_reset()
518 src = isp.source_reset()
525 print('Input source was:\n', src)
519 print('Input source was:\n', src)
526 print('Raw source was:\n', raw)
520 print('Raw source was:\n', raw)
527 except EOFError:
521 except EOFError:
528 print('Bye')
522 print('Bye')
529
523
530 # Tests for cell magics support
524 # Tests for cell magics support
531
525
532 def test_last_blank():
526 def test_last_blank():
533 assert isp.last_blank("") is False
527 assert isp.last_blank("") is False
534 assert isp.last_blank("abc") is False
528 assert isp.last_blank("abc") is False
535 assert isp.last_blank("abc\n") is False
529 assert isp.last_blank("abc\n") is False
536 assert isp.last_blank("abc\na") is False
530 assert isp.last_blank("abc\na") is False
537
531
538 assert isp.last_blank("\n") is True
532 assert isp.last_blank("\n") is True
539 assert isp.last_blank("\n ") is True
533 assert isp.last_blank("\n ") is True
540 assert isp.last_blank("abc\n ") is True
534 assert isp.last_blank("abc\n ") is True
541 assert isp.last_blank("abc\n\n") is True
535 assert isp.last_blank("abc\n\n") is True
542 assert isp.last_blank("abc\nd\n\n") is True
536 assert isp.last_blank("abc\nd\n\n") is True
543 assert isp.last_blank("abc\nd\ne\n\n") is True
537 assert isp.last_blank("abc\nd\ne\n\n") is True
544 assert isp.last_blank("abc \n \n \n\n") is True
538 assert isp.last_blank("abc \n \n \n\n") is True
545
539
546
540
547 def test_last_two_blanks():
541 def test_last_two_blanks():
548 assert isp.last_two_blanks("") is False
542 assert isp.last_two_blanks("") is False
549 assert isp.last_two_blanks("abc") is False
543 assert isp.last_two_blanks("abc") is False
550 assert isp.last_two_blanks("abc\n") is False
544 assert isp.last_two_blanks("abc\n") is False
551 assert isp.last_two_blanks("abc\n\na") is False
545 assert isp.last_two_blanks("abc\n\na") is False
552 assert isp.last_two_blanks("abc\n \n") is False
546 assert isp.last_two_blanks("abc\n \n") is False
553 assert isp.last_two_blanks("abc\n\n") is False
547 assert isp.last_two_blanks("abc\n\n") is False
554
548
555 assert isp.last_two_blanks("\n\n") is True
549 assert isp.last_two_blanks("\n\n") is True
556 assert isp.last_two_blanks("\n\n ") is True
550 assert isp.last_two_blanks("\n\n ") is True
557 assert isp.last_two_blanks("\n \n") is True
551 assert isp.last_two_blanks("\n \n") is True
558 assert isp.last_two_blanks("abc\n\n ") is True
552 assert isp.last_two_blanks("abc\n\n ") is True
559 assert isp.last_two_blanks("abc\n\n\n") is True
553 assert isp.last_two_blanks("abc\n\n\n") is True
560 assert isp.last_two_blanks("abc\n\n \n") is True
554 assert isp.last_two_blanks("abc\n\n \n") is True
561 assert isp.last_two_blanks("abc\n\n \n ") is True
555 assert isp.last_two_blanks("abc\n\n \n ") is True
562 assert isp.last_two_blanks("abc\n\n \n \n") is True
556 assert isp.last_two_blanks("abc\n\n \n \n") is True
563 assert isp.last_two_blanks("abc\nd\n\n\n") is True
557 assert isp.last_two_blanks("abc\nd\n\n\n") is True
564 assert isp.last_two_blanks("abc\nd\ne\nf\n\n\n") is True
558 assert isp.last_two_blanks("abc\nd\ne\nf\n\n\n") is True
565
559
566
560
567 class CellMagicsCommon(object):
561 class CellMagicsCommon(object):
568
562
569 def test_whole_cell(self):
563 def test_whole_cell(self):
570 src = "%%cellm line\nbody\n"
564 src = "%%cellm line\nbody\n"
571 out = self.sp.transform_cell(src)
565 out = self.sp.transform_cell(src)
572 ref = "get_ipython().run_cell_magic('cellm', 'line', 'body')\n"
566 ref = "get_ipython().run_cell_magic('cellm', 'line', 'body')\n"
573 assert out == ref
567 assert out == ref
574
568
575 def test_cellmagic_help(self):
569 def test_cellmagic_help(self):
576 self.sp.push('%%cellm?')
570 self.sp.push('%%cellm?')
577 assert self.sp.push_accepts_more() is False
571 assert self.sp.push_accepts_more() is False
578
572
579 def tearDown(self):
573 def tearDown(self):
580 self.sp.reset()
574 self.sp.reset()
581
575
582
576
583 class CellModeCellMagics(CellMagicsCommon, unittest.TestCase):
577 class CellModeCellMagics(CellMagicsCommon, unittest.TestCase):
584 sp = isp.IPythonInputSplitter(line_input_checker=False)
578 sp = isp.IPythonInputSplitter(line_input_checker=False)
585
579
586 def test_incremental(self):
580 def test_incremental(self):
587 sp = self.sp
581 sp = self.sp
588 sp.push("%%cellm firstline\n")
582 sp.push("%%cellm firstline\n")
589 assert sp.push_accepts_more() is True # 1
583 assert sp.push_accepts_more() is True # 1
590 sp.push("line2\n")
584 sp.push("line2\n")
591 assert sp.push_accepts_more() is True # 2
585 assert sp.push_accepts_more() is True # 2
592 sp.push("\n")
586 sp.push("\n")
593 # This should accept a blank line and carry on until the cell is reset
587 # This should accept a blank line and carry on until the cell is reset
594 assert sp.push_accepts_more() is True # 3
588 assert sp.push_accepts_more() is True # 3
595
589
596 def test_no_strip_coding(self):
590 def test_no_strip_coding(self):
597 src = '\n'.join([
591 src = '\n'.join([
598 '%%writefile foo.py',
592 '%%writefile foo.py',
599 '# coding: utf-8',
593 '# coding: utf-8',
600 'print(u"üñîçø∂é")',
594 'print(u"üñîçø∂é")',
601 ])
595 ])
602 out = self.sp.transform_cell(src)
596 out = self.sp.transform_cell(src)
603 assert "# coding: utf-8" in out
597 assert "# coding: utf-8" in out
604
598
605
599
606 class LineModeCellMagics(CellMagicsCommon, unittest.TestCase):
600 class LineModeCellMagics(CellMagicsCommon, unittest.TestCase):
607 sp = isp.IPythonInputSplitter(line_input_checker=True)
601 sp = isp.IPythonInputSplitter(line_input_checker=True)
608
602
609 def test_incremental(self):
603 def test_incremental(self):
610 sp = self.sp
604 sp = self.sp
611 sp.push("%%cellm line2\n")
605 sp.push("%%cellm line2\n")
612 assert sp.push_accepts_more() is True # 1
606 assert sp.push_accepts_more() is True # 1
613 sp.push("\n")
607 sp.push("\n")
614 # In this case, a blank line should end the cell magic
608 # In this case, a blank line should end the cell magic
615 assert sp.push_accepts_more() is False # 2
609 assert sp.push_accepts_more() is False # 2
616
610
617
611
618 indentation_samples = [
612 indentation_samples = [
619 ('a = 1', 0),
613 ('a = 1', 0),
620 ('for a in b:', 4),
614 ('for a in b:', 4),
621 ('def f():', 4),
615 ('def f():', 4),
622 ('def f(): #comment', 4),
616 ('def f(): #comment', 4),
623 ('a = ":#not a comment"', 0),
617 ('a = ":#not a comment"', 0),
624 ('def f():\n a = 1', 4),
618 ('def f():\n a = 1', 4),
625 ('def f():\n return 1', 0),
619 ('def f():\n return 1', 0),
626 ('for a in b:\n'
620 ('for a in b:\n'
627 ' if a < 0:'
621 ' if a < 0:'
628 ' continue', 3),
622 ' continue', 3),
629 ('a = {', 4),
623 ('a = {', 4),
630 ('a = {\n'
624 ('a = {\n'
631 ' 1,', 5),
625 ' 1,', 5),
632 ('b = """123', 0),
626 ('b = """123', 0),
633 ('', 0),
627 ('', 0),
634 ('def f():\n pass', 0),
628 ('def f():\n pass', 0),
635 ('class Bar:\n def f():\n pass', 4),
629 ('class Bar:\n def f():\n pass', 4),
636 ('class Bar:\n def f():\n raise', 4),
630 ('class Bar:\n def f():\n raise', 4),
637 ]
631 ]
638
632
639 def test_find_next_indent():
633 def test_find_next_indent():
640 for code, exp in indentation_samples:
634 for code, exp in indentation_samples:
641 res = isp.find_next_indent(code)
635 res = isp.find_next_indent(code)
642 msg = "{!r} != {!r} (expected)\n Code: {!r}".format(res, exp, code)
636 msg = "{!r} != {!r} (expected)\n Code: {!r}".format(res, exp, code)
643 assert res == exp, msg
637 assert res == exp, msg
General Comments 0
You need to be logged in to leave comments. Login now