##// END OF EJS Templates
please linter
Matthias Bussonnier -
Show More
@@ -1,15 +1,15 b''
1 # PYTHON_ARGCOMPLETE_OK
1 # PYTHON_ARGCOMPLETE_OK
2 # encoding: utf-8
2 # encoding: utf-8
3 """Terminal-based IPython entry point.
3 """Terminal-based IPython entry point.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 # -----------------------------------------------------------------------------
6 # Copyright (c) 2012, IPython Development Team.
6 # Copyright (c) 2012, IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 # -----------------------------------------------------------------------------
12
12
13 from IPython import start_ipython
13 from IPython import start_ipython
14
14
15 start_ipython()
15 start_ipython()
@@ -1,795 +1,795 b''
1 """DEPRECATED: Input handling and transformation machinery.
1 """DEPRECATED: Input handling and transformation machinery.
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 The first class in this module, :class:`InputSplitter`, is designed to tell when
5 The first class in this module, :class:`InputSplitter`, is designed to tell when
6 input from a line-oriented frontend is complete and should be executed, and when
6 input from a line-oriented frontend is complete and should be executed, and when
7 the user should be prompted for another line of code instead. The name 'input
7 the user should be prompted for another line of code instead. The name 'input
8 splitter' is largely for historical reasons.
8 splitter' is largely for historical reasons.
9
9
10 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
10 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
11 with full support for the extended IPython syntax (magics, system calls, etc).
11 with full support for the extended IPython syntax (magics, system calls, etc).
12 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
12 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
13 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
13 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
14 and stores the results.
14 and stores the results.
15
15
16 For more details, see the class docstrings below.
16 For more details, see the class docstrings below.
17 """
17 """
18
18
19 from warnings import warn
19 from warnings import warn
20
20
21 warn('IPython.core.inputsplitter is deprecated since IPython 7 in favor of `IPython.core.inputtransformer2`',
21 warn('IPython.core.inputsplitter is deprecated since IPython 7 in favor of `IPython.core.inputtransformer2`',
22 DeprecationWarning)
22 DeprecationWarning)
23
23
24 # Copyright (c) IPython Development Team.
24 # Copyright (c) IPython Development Team.
25 # Distributed under the terms of the Modified BSD License.
25 # Distributed under the terms of the Modified BSD License.
26 import ast
26 import ast
27 import codeop
27 import codeop
28 import io
28 import io
29 import re
29 import re
30 import sys
30 import sys
31 import tokenize
31 import tokenize
32 import warnings
32 import warnings
33
33
34 from typing import List, Tuple, Union, Optional
34 from typing import List, Tuple, Union, Optional
35 from types import CodeType
35 from types import CodeType
36
36
37 from IPython.core.inputtransformer import (leading_indent,
37 from IPython.core.inputtransformer import (leading_indent,
38 classic_prompt,
38 classic_prompt,
39 ipy_prompt,
39 ipy_prompt,
40 cellmagic,
40 cellmagic,
41 assemble_logical_lines,
41 assemble_logical_lines,
42 help_end,
42 help_end,
43 escaped_commands,
43 escaped_commands,
44 assign_from_magic,
44 assign_from_magic,
45 assign_from_system,
45 assign_from_system,
46 assemble_python_lines,
46 assemble_python_lines,
47 )
47 )
48 from IPython.utils import tokenutil
48 from IPython.utils import tokenutil
49
49
50 # These are available in this module for backwards compatibility.
50 # These are available in this module for backwards compatibility.
51 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
51 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
52 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
52 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
53 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
53 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Utilities
56 # Utilities
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59 # FIXME: These are general-purpose utilities that later can be moved to the
59 # FIXME: These are general-purpose utilities that later can be moved to the
60 # general ward. Kept here for now because we're being very strict about test
60 # general ward. Kept here for now because we're being very strict about test
61 # coverage with this code, and this lets us ensure that we keep 100% coverage
61 # coverage with this code, and this lets us ensure that we keep 100% coverage
62 # while developing.
62 # while developing.
63
63
64 # compiled regexps for autoindent management
64 # compiled regexps for autoindent management
65 dedent_re = re.compile('|'.join([
65 dedent_re = re.compile('|'.join([
66 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
66 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
67 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
67 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
68 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
68 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
69 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
69 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
70 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
70 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
71 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
71 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
72 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
72 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
73 ]))
73 ]))
74 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
74 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
75
75
76 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
76 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
77 # before pure comments
77 # before pure comments
78 comment_line_re = re.compile(r'^\s*\#')
78 comment_line_re = re.compile(r'^\s*\#')
79
79
80
80
81 def num_ini_spaces(s):
81 def num_ini_spaces(s):
82 """Return the number of initial spaces in a string.
82 """Return the number of initial spaces in a string.
83
83
84 Note that tabs are counted as a single space. For now, we do *not* support
84 Note that tabs are counted as a single space. For now, we do *not* support
85 mixing of tabs and spaces in the user's input.
85 mixing of tabs and spaces in the user's input.
86
86
87 Parameters
87 Parameters
88 ----------
88 ----------
89 s : string
89 s : string
90
90
91 Returns
91 Returns
92 -------
92 -------
93 n : int
93 n : int
94 """
94 """
95 warnings.warn(
95 warnings.warn(
96 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
96 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
97 "It is considered fro removal in in future version. "
97 "It is considered fro removal in in future version. "
98 "Please open an issue if you believe it should be kept.",
98 "Please open an issue if you believe it should be kept.",
99 stacklevel=2,
99 stacklevel=2,
100 category=PendingDeprecationWarning,
100 category=PendingDeprecationWarning,
101 )
101 )
102 ini_spaces = ini_spaces_re.match(s)
102 ini_spaces = ini_spaces_re.match(s)
103 if ini_spaces:
103 if ini_spaces:
104 return ini_spaces.end()
104 return ini_spaces.end()
105 else:
105 else:
106 return 0
106 return 0
107
107
108 # Fake token types for partial_tokenize:
108 # Fake token types for partial_tokenize:
109 INCOMPLETE_STRING = tokenize.N_TOKENS
109 INCOMPLETE_STRING = tokenize.N_TOKENS
110 IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
110 IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
111
111
112 # The 2 classes below have the same API as TokenInfo, but don't try to look up
112 # The 2 classes below have the same API as TokenInfo, but don't try to look up
113 # a token type name that they won't find.
113 # a token type name that they won't find.
114 class IncompleteString:
114 class IncompleteString:
115 type = exact_type = INCOMPLETE_STRING
115 type = exact_type = INCOMPLETE_STRING
116 def __init__(self, s, start, end, line):
116 def __init__(self, s, start, end, line):
117 self.s = s
117 self.s = s
118 self.start = start
118 self.start = start
119 self.end = end
119 self.end = end
120 self.line = line
120 self.line = line
121
121
122 class InMultilineStatement:
122 class InMultilineStatement:
123 type = exact_type = IN_MULTILINE_STATEMENT
123 type = exact_type = IN_MULTILINE_STATEMENT
124 def __init__(self, pos, line):
124 def __init__(self, pos, line):
125 self.s = ''
125 self.s = ''
126 self.start = self.end = pos
126 self.start = self.end = pos
127 self.line = line
127 self.line = line
128
128
129 def partial_tokens(s):
129 def partial_tokens(s):
130 """Iterate over tokens from a possibly-incomplete string of code.
130 """Iterate over tokens from a possibly-incomplete string of code.
131
131
132 This adds two special token types: INCOMPLETE_STRING and
132 This adds two special token types: INCOMPLETE_STRING and
133 IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
133 IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
134 represent the two main ways for code to be incomplete.
134 represent the two main ways for code to be incomplete.
135 """
135 """
136 readline = io.StringIO(s).readline
136 readline = io.StringIO(s).readline
137 token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
137 token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
138 try:
138 try:
139 for token in tokenutil.generate_tokens_catch_errors(readline):
139 for token in tokenutil.generate_tokens_catch_errors(readline):
140 yield token
140 yield token
141 except tokenize.TokenError as e:
141 except tokenize.TokenError as e:
142 # catch EOF error
142 # catch EOF error
143 lines = s.splitlines(keepends=True)
143 lines = s.splitlines(keepends=True)
144 end = len(lines), len(lines[-1])
144 end = len(lines), len(lines[-1])
145 if 'multi-line string' in e.args[0]:
145 if 'multi-line string' in e.args[0]:
146 l, c = start = token.end
146 l, c = start = token.end
147 s = lines[l-1][c:] + ''.join(lines[l:])
147 s = lines[l-1][c:] + ''.join(lines[l:])
148 yield IncompleteString(s, start, end, lines[-1])
148 yield IncompleteString(s, start, end, lines[-1])
149 elif 'multi-line statement' in e.args[0]:
149 elif 'multi-line statement' in e.args[0]:
150 yield InMultilineStatement(end, lines[-1])
150 yield InMultilineStatement(end, lines[-1])
151 else:
151 else:
152 raise
152 raise
153
153
154 def find_next_indent(code) -> int:
154 def find_next_indent(code) -> int:
155 """Find the number of spaces for the next line of indentation"""
155 """Find the number of spaces for the next line of indentation"""
156 tokens = list(partial_tokens(code))
156 tokens = list(partial_tokens(code))
157 if tokens[-1].type == tokenize.ENDMARKER:
157 if tokens[-1].type == tokenize.ENDMARKER:
158 tokens.pop()
158 tokens.pop()
159 if not tokens:
159 if not tokens:
160 return 0
160 return 0
161
161
162 while tokens[-1].type in {
162 while tokens[-1].type in {
163 tokenize.DEDENT,
163 tokenize.DEDENT,
164 tokenize.NEWLINE,
164 tokenize.NEWLINE,
165 tokenize.COMMENT,
165 tokenize.COMMENT,
166 tokenize.ERRORTOKEN,
166 tokenize.ERRORTOKEN,
167 }:
167 }:
168 tokens.pop()
168 tokens.pop()
169
169
170 # Starting in Python 3.12, the tokenize module adds implicit newlines at the end
170 # Starting in Python 3.12, the tokenize module adds implicit newlines at the end
171 # of input. We need to remove those if we're in a multiline statement
171 # of input. We need to remove those if we're in a multiline statement
172 if tokens[-1].type == IN_MULTILINE_STATEMENT:
172 if tokens[-1].type == IN_MULTILINE_STATEMENT:
173 while tokens[-2].type in {tokenize.NL}:
173 while tokens[-2].type in {tokenize.NL}:
174 tokens.pop(-2)
174 tokens.pop(-2)
175
175
176
176
177 if tokens[-1].type == INCOMPLETE_STRING:
177 if tokens[-1].type == INCOMPLETE_STRING:
178 # Inside a multiline string
178 # Inside a multiline string
179 return 0
179 return 0
180
180
181 # Find the indents used before
181 # Find the indents used before
182 prev_indents = [0]
182 prev_indents = [0]
183 def _add_indent(n):
183 def _add_indent(n):
184 if n != prev_indents[-1]:
184 if n != prev_indents[-1]:
185 prev_indents.append(n)
185 prev_indents.append(n)
186
186
187 tokiter = iter(tokens)
187 tokiter = iter(tokens)
188 for tok in tokiter:
188 for tok in tokiter:
189 if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
189 if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
190 _add_indent(tok.end[1])
190 _add_indent(tok.end[1])
191 elif (tok.type == tokenize.NL):
191 elif (tok.type == tokenize.NL):
192 try:
192 try:
193 _add_indent(next(tokiter).start[1])
193 _add_indent(next(tokiter).start[1])
194 except StopIteration:
194 except StopIteration:
195 break
195 break
196
196
197 last_indent = prev_indents.pop()
197 last_indent = prev_indents.pop()
198
198
199 # If we've just opened a multiline statement (e.g. 'a = ['), indent more
199 # If we've just opened a multiline statement (e.g. 'a = ['), indent more
200 if tokens[-1].type == IN_MULTILINE_STATEMENT:
200 if tokens[-1].type == IN_MULTILINE_STATEMENT:
201 if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
201 if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
202 return last_indent + 4
202 return last_indent + 4
203 return last_indent
203 return last_indent
204
204
205 if tokens[-1].exact_type == tokenize.COLON:
205 if tokens[-1].exact_type == tokenize.COLON:
206 # Line ends with colon - indent
206 # Line ends with colon - indent
207 return last_indent + 4
207 return last_indent + 4
208
208
209 if last_indent:
209 if last_indent:
210 # Examine the last line for dedent cues - statements like return or
210 # Examine the last line for dedent cues - statements like return or
211 # raise which normally end a block of code.
211 # raise which normally end a block of code.
212 last_line_starts = 0
212 last_line_starts = 0
213 for i, tok in enumerate(tokens):
213 for i, tok in enumerate(tokens):
214 if tok.type == tokenize.NEWLINE:
214 if tok.type == tokenize.NEWLINE:
215 last_line_starts = i + 1
215 last_line_starts = i + 1
216
216
217 last_line_tokens = tokens[last_line_starts:]
217 last_line_tokens = tokens[last_line_starts:]
218 names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
218 names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
219 if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
219 if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
220 # Find the most recent indentation less than the current level
220 # Find the most recent indentation less than the current level
221 for indent in reversed(prev_indents):
221 for indent in reversed(prev_indents):
222 if indent < last_indent:
222 if indent < last_indent:
223 return indent
223 return indent
224
224
225 return last_indent
225 return last_indent
226
226
227
227
228 def last_blank(src):
228 def last_blank(src):
229 """Determine if the input source ends in a blank.
229 """Determine if the input source ends in a blank.
230
230
231 A blank is either a newline or a line consisting of whitespace.
231 A blank is either a newline or a line consisting of whitespace.
232
232
233 Parameters
233 Parameters
234 ----------
234 ----------
235 src : string
235 src : string
236 A single or multiline string.
236 A single or multiline string.
237 """
237 """
238 if not src: return False
238 if not src: return False
239 ll = src.splitlines()[-1]
239 ll = src.splitlines()[-1]
240 return (ll == '') or ll.isspace()
240 return (ll == '') or ll.isspace()
241
241
242
242
243 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
243 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
244 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
244 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
245
245
246 def last_two_blanks(src):
246 def last_two_blanks(src):
247 """Determine if the input source ends in two blanks.
247 """Determine if the input source ends in two blanks.
248
248
249 A blank is either a newline or a line consisting of whitespace.
249 A blank is either a newline or a line consisting of whitespace.
250
250
251 Parameters
251 Parameters
252 ----------
252 ----------
253 src : string
253 src : string
254 A single or multiline string.
254 A single or multiline string.
255 """
255 """
256 if not src: return False
256 if not src: return False
257 # The logic here is tricky: I couldn't get a regexp to work and pass all
257 # The logic here is tricky: I couldn't get a regexp to work and pass all
258 # the tests, so I took a different approach: split the source by lines,
258 # the tests, so I took a different approach: split the source by lines,
259 # grab the last two and prepend '###\n' as a stand-in for whatever was in
259 # grab the last two and prepend '###\n' as a stand-in for whatever was in
260 # the body before the last two lines. Then, with that structure, it's
260 # the body before the last two lines. Then, with that structure, it's
261 # possible to analyze with two regexps. Not the most elegant solution, but
261 # possible to analyze with two regexps. Not the most elegant solution, but
262 # it works. If anyone tries to change this logic, make sure to validate
262 # it works. If anyone tries to change this logic, make sure to validate
263 # the whole test suite first!
263 # the whole test suite first!
264 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
264 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
265 return (bool(last_two_blanks_re.match(new_src)) or
265 return (bool(last_two_blanks_re.match(new_src)) or
266 bool(last_two_blanks_re2.match(new_src)) )
266 bool(last_two_blanks_re2.match(new_src)) )
267
267
268
268
269 def remove_comments(src):
269 def remove_comments(src):
270 """Remove all comments from input source.
270 """Remove all comments from input source.
271
271
272 Note: comments are NOT recognized inside of strings!
272 Note: comments are NOT recognized inside of strings!
273
273
274 Parameters
274 Parameters
275 ----------
275 ----------
276 src : string
276 src : string
277 A single or multiline input string.
277 A single or multiline input string.
278
278
279 Returns
279 Returns
280 -------
280 -------
281 String with all Python comments removed.
281 String with all Python comments removed.
282 """
282 """
283
283
284 return re.sub('#.*', '', src)
284 return re.sub('#.*', '', src)
285
285
286
286
287 def get_input_encoding():
287 def get_input_encoding():
288 """Return the default standard input encoding.
288 """Return the default standard input encoding.
289
289
290 If sys.stdin has no encoding, 'ascii' is returned."""
290 If sys.stdin has no encoding, 'ascii' is returned."""
291 # There are strange environments for which sys.stdin.encoding is None. We
291 # There are strange environments for which sys.stdin.encoding is None. We
292 # ensure that a valid encoding is returned.
292 # ensure that a valid encoding is returned.
293 encoding = getattr(sys.stdin, 'encoding', None)
293 encoding = getattr(sys.stdin, 'encoding', None)
294 if encoding is None:
294 if encoding is None:
295 encoding = 'ascii'
295 encoding = 'ascii'
296 return encoding
296 return encoding
297
297
298 #-----------------------------------------------------------------------------
298 #-----------------------------------------------------------------------------
299 # Classes and functions for normal Python syntax handling
299 # Classes and functions for normal Python syntax handling
300 #-----------------------------------------------------------------------------
300 #-----------------------------------------------------------------------------
301
301
302 class InputSplitter(object):
302 class InputSplitter(object):
303 r"""An object that can accumulate lines of Python source before execution.
303 r"""An object that can accumulate lines of Python source before execution.
304
304
305 This object is designed to be fed python source line-by-line, using
305 This object is designed to be fed python source line-by-line, using
306 :meth:`push`. It will return on each push whether the currently pushed
306 :meth:`push`. It will return on each push whether the currently pushed
307 code could be executed already. In addition, it provides a method called
307 code could be executed already. In addition, it provides a method called
308 :meth:`push_accepts_more` that can be used to query whether more input
308 :meth:`push_accepts_more` that can be used to query whether more input
309 can be pushed into a single interactive block.
309 can be pushed into a single interactive block.
310
310
311 This is a simple example of how an interactive terminal-based client can use
311 This is a simple example of how an interactive terminal-based client can use
312 this tool::
312 this tool::
313
313
314 isp = InputSplitter()
314 isp = InputSplitter()
315 while isp.push_accepts_more():
315 while isp.push_accepts_more():
316 indent = ' '*isp.indent_spaces
316 indent = ' '*isp.indent_spaces
317 prompt = '>>> ' + indent
317 prompt = '>>> ' + indent
318 line = indent + raw_input(prompt)
318 line = indent + raw_input(prompt)
319 isp.push(line)
319 isp.push(line)
320 print 'Input source was:\n', isp.source_reset(),
320 print 'Input source was:\n', isp.source_reset(),
321 """
321 """
322 # A cache for storing the current indentation
322 # A cache for storing the current indentation
323 # The first value stores the most recently processed source input
323 # The first value stores the most recently processed source input
324 # The second value is the number of spaces for the current indentation
324 # The second value is the number of spaces for the current indentation
325 # If self.source matches the first value, the second value is a valid
325 # If self.source matches the first value, the second value is a valid
326 # current indentation. Otherwise, the cache is invalid and the indentation
326 # current indentation. Otherwise, the cache is invalid and the indentation
327 # must be recalculated.
327 # must be recalculated.
328 _indent_spaces_cache: Union[Tuple[None, None], Tuple[str, int]] = None, None
328 _indent_spaces_cache: Union[Tuple[None, None], Tuple[str, int]] = None, None
329 # String, indicating the default input encoding. It is computed by default
329 # String, indicating the default input encoding. It is computed by default
330 # at initialization time via get_input_encoding(), but it can be reset by a
330 # at initialization time via get_input_encoding(), but it can be reset by a
331 # client with specific knowledge of the encoding.
331 # client with specific knowledge of the encoding.
332 encoding = ''
332 encoding = ''
333 # String where the current full source input is stored, properly encoded.
333 # String where the current full source input is stored, properly encoded.
334 # Reading this attribute is the normal way of querying the currently pushed
334 # Reading this attribute is the normal way of querying the currently pushed
335 # source code, that has been properly encoded.
335 # source code, that has been properly encoded.
336 source: str = ""
336 source: str = ""
337 # Code object corresponding to the current source. It is automatically
337 # Code object corresponding to the current source. It is automatically
338 # synced to the source, so it can be queried at any time to obtain the code
338 # synced to the source, so it can be queried at any time to obtain the code
339 # object; it will be None if the source doesn't compile to valid Python.
339 # object; it will be None if the source doesn't compile to valid Python.
340 code:Optional[CodeType] = None
340 code: Optional[CodeType] = None
341
341
342 # Private attributes
342 # Private attributes
343
343
344 # List with lines of input accumulated so far
344 # List with lines of input accumulated so far
345 _buffer: List[str]
345 _buffer: List[str]
346 # Command compiler
346 # Command compiler
347 _compile: codeop.CommandCompiler
347 _compile: codeop.CommandCompiler
348 # Boolean indicating whether the current block is complete
348 # Boolean indicating whether the current block is complete
349 _is_complete:Optional[bool] = None
349 _is_complete: Optional[bool] = None
350 # Boolean indicating whether the current block has an unrecoverable syntax error
350 # Boolean indicating whether the current block has an unrecoverable syntax error
351 _is_invalid:bool = False
351 _is_invalid: bool = False
352
352
353 def __init__(self) -> None:
353 def __init__(self) -> None:
354 """Create a new InputSplitter instance."""
354 """Create a new InputSplitter instance."""
355 self._buffer = []
355 self._buffer = []
356 self._compile = codeop.CommandCompiler()
356 self._compile = codeop.CommandCompiler()
357 self.encoding = get_input_encoding()
357 self.encoding = get_input_encoding()
358
358
359 def reset(self):
359 def reset(self):
360 """Reset the input buffer and associated state."""
360 """Reset the input buffer and associated state."""
361 self._buffer[:] = []
361 self._buffer[:] = []
362 self.source = ''
362 self.source = ''
363 self.code = None
363 self.code = None
364 self._is_complete = False
364 self._is_complete = False
365 self._is_invalid = False
365 self._is_invalid = False
366
366
367 def source_reset(self):
367 def source_reset(self):
368 """Return the input source and perform a full reset.
368 """Return the input source and perform a full reset.
369 """
369 """
370 out = self.source
370 out = self.source
371 self.reset()
371 self.reset()
372 return out
372 return out
373
373
374 def check_complete(self, source):
374 def check_complete(self, source):
375 """Return whether a block of code is ready to execute, or should be continued
375 """Return whether a block of code is ready to execute, or should be continued
376
376
377 This is a non-stateful API, and will reset the state of this InputSplitter.
377 This is a non-stateful API, and will reset the state of this InputSplitter.
378
378
379 Parameters
379 Parameters
380 ----------
380 ----------
381 source : string
381 source : string
382 Python input code, which can be multiline.
382 Python input code, which can be multiline.
383
383
384 Returns
384 Returns
385 -------
385 -------
386 status : str
386 status : str
387 One of 'complete', 'incomplete', or 'invalid' if source is not a
387 One of 'complete', 'incomplete', or 'invalid' if source is not a
388 prefix of valid code.
388 prefix of valid code.
389 indent_spaces : int or None
389 indent_spaces : int or None
390 The number of spaces by which to indent the next line of code. If
390 The number of spaces by which to indent the next line of code. If
391 status is not 'incomplete', this is None.
391 status is not 'incomplete', this is None.
392 """
392 """
393 self.reset()
393 self.reset()
394 try:
394 try:
395 self.push(source)
395 self.push(source)
396 except SyntaxError:
396 except SyntaxError:
397 # Transformers in IPythonInputSplitter can raise SyntaxError,
397 # Transformers in IPythonInputSplitter can raise SyntaxError,
398 # which push() will not catch.
398 # which push() will not catch.
399 return 'invalid', None
399 return 'invalid', None
400 else:
400 else:
401 if self._is_invalid:
401 if self._is_invalid:
402 return 'invalid', None
402 return 'invalid', None
403 elif self.push_accepts_more():
403 elif self.push_accepts_more():
404 return 'incomplete', self.get_indent_spaces()
404 return 'incomplete', self.get_indent_spaces()
405 else:
405 else:
406 return 'complete', None
406 return 'complete', None
407 finally:
407 finally:
408 self.reset()
408 self.reset()
409
409
410 def push(self, lines:str) -> bool:
410 def push(self, lines:str) -> bool:
411 """Push one or more lines of input.
411 """Push one or more lines of input.
412
412
413 This stores the given lines and returns a status code indicating
413 This stores the given lines and returns a status code indicating
414 whether the code forms a complete Python block or not.
414 whether the code forms a complete Python block or not.
415
415
416 Any exceptions generated in compilation are swallowed, but if an
416 Any exceptions generated in compilation are swallowed, but if an
417 exception was produced, the method returns True.
417 exception was produced, the method returns True.
418
418
419 Parameters
419 Parameters
420 ----------
420 ----------
421 lines : string
421 lines : string
422 One or more lines of Python input.
422 One or more lines of Python input.
423
423
424 Returns
424 Returns
425 -------
425 -------
426 is_complete : boolean
426 is_complete : boolean
427 True if the current input source (the result of the current input
427 True if the current input source (the result of the current input
428 plus prior inputs) forms a complete Python execution block. Note that
428 plus prior inputs) forms a complete Python execution block. Note that
429 this value is also stored as a private attribute (``_is_complete``), so it
429 this value is also stored as a private attribute (``_is_complete``), so it
430 can be queried at any time.
430 can be queried at any time.
431 """
431 """
432 assert isinstance(lines, str)
432 assert isinstance(lines, str)
433 self._store(lines)
433 self._store(lines)
434 source = self.source
434 source = self.source
435
435
436 # Before calling _compile(), reset the code object to None so that if an
436 # Before calling _compile(), reset the code object to None so that if an
437 # exception is raised in compilation, we don't mislead by having
437 # exception is raised in compilation, we don't mislead by having
438 # inconsistent code/source attributes.
438 # inconsistent code/source attributes.
439 self.code, self._is_complete = None, None
439 self.code, self._is_complete = None, None
440 self._is_invalid = False
440 self._is_invalid = False
441
441
442 # Honor termination lines properly
442 # Honor termination lines properly
443 if source.endswith('\\\n'):
443 if source.endswith('\\\n'):
444 return False
444 return False
445
445
446 try:
446 try:
447 with warnings.catch_warnings():
447 with warnings.catch_warnings():
448 warnings.simplefilter('error', SyntaxWarning)
448 warnings.simplefilter('error', SyntaxWarning)
449 self.code = self._compile(source, symbol="exec")
449 self.code = self._compile(source, symbol="exec")
450 # Invalid syntax can produce any of a number of different errors from
450 # Invalid syntax can produce any of a number of different errors from
451 # inside the compiler, so we have to catch them all. Syntax errors
451 # inside the compiler, so we have to catch them all. Syntax errors
452 # immediately produce a 'ready' block, so the invalid Python can be
452 # immediately produce a 'ready' block, so the invalid Python can be
453 # sent to the kernel for evaluation with possible ipython
453 # sent to the kernel for evaluation with possible ipython
454 # special-syntax conversion.
454 # special-syntax conversion.
455 except (SyntaxError, OverflowError, ValueError, TypeError,
455 except (SyntaxError, OverflowError, ValueError, TypeError,
456 MemoryError, SyntaxWarning):
456 MemoryError, SyntaxWarning):
457 self._is_complete = True
457 self._is_complete = True
458 self._is_invalid = True
458 self._is_invalid = True
459 else:
459 else:
460 # Compilation didn't produce any exceptions (though it may not have
460 # Compilation didn't produce any exceptions (though it may not have
461 # given a complete code object)
461 # given a complete code object)
462 self._is_complete = self.code is not None
462 self._is_complete = self.code is not None
463
463
464 return self._is_complete
464 return self._is_complete
465
465
466 def push_accepts_more(self):
466 def push_accepts_more(self):
467 """Return whether a block of interactive input can accept more input.
467 """Return whether a block of interactive input can accept more input.
468
468
469 This method is meant to be used by line-oriented frontends, who need to
469 This method is meant to be used by line-oriented frontends, who need to
470 guess whether a block is complete or not based solely on prior and
470 guess whether a block is complete or not based solely on prior and
471 current input lines. The InputSplitter considers it has a complete
471 current input lines. The InputSplitter considers it has a complete
472 interactive block and will not accept more input when either:
472 interactive block and will not accept more input when either:
473
473
474 * A SyntaxError is raised
474 * A SyntaxError is raised
475
475
476 * The code is complete and consists of a single line or a single
476 * The code is complete and consists of a single line or a single
477 non-compound statement
477 non-compound statement
478
478
479 * The code is complete and has a blank line at the end
479 * The code is complete and has a blank line at the end
480
480
481 If the current input produces a syntax error, this method immediately
481 If the current input produces a syntax error, this method immediately
482 returns False but does *not* raise the syntax error exception, as
482 returns False but does *not* raise the syntax error exception, as
483 typically clients will want to send invalid syntax to an execution
483 typically clients will want to send invalid syntax to an execution
484 backend which might convert the invalid syntax into valid Python via
484 backend which might convert the invalid syntax into valid Python via
485 one of the dynamic IPython mechanisms.
485 one of the dynamic IPython mechanisms.
486 """
486 """
487
487
488 # With incomplete input, unconditionally accept more
488 # With incomplete input, unconditionally accept more
489 # A syntax error also sets _is_complete to True - see push()
489 # A syntax error also sets _is_complete to True - see push()
490 if not self._is_complete:
490 if not self._is_complete:
491 #print("Not complete") # debug
491 #print("Not complete") # debug
492 return True
492 return True
493
493
494 # The user can make any (complete) input execute by leaving a blank line
494 # The user can make any (complete) input execute by leaving a blank line
495 last_line = self.source.splitlines()[-1]
495 last_line = self.source.splitlines()[-1]
496 if (not last_line) or last_line.isspace():
496 if (not last_line) or last_line.isspace():
497 #print("Blank line") # debug
497 #print("Blank line") # debug
498 return False
498 return False
499
499
500 # If there's just a single line or AST node, and we're flush left, as is
500 # If there's just a single line or AST node, and we're flush left, as is
501 # the case after a simple statement such as 'a=1', we want to execute it
501 # the case after a simple statement such as 'a=1', we want to execute it
502 # straight away.
502 # straight away.
503 if self.get_indent_spaces() == 0:
503 if self.get_indent_spaces() == 0:
504 if len(self.source.splitlines()) <= 1:
504 if len(self.source.splitlines()) <= 1:
505 return False
505 return False
506
506
507 try:
507 try:
508 code_ast = ast.parse("".join(self._buffer))
508 code_ast = ast.parse("".join(self._buffer))
509 except Exception:
509 except Exception:
510 #print("Can't parse AST") # debug
510 #print("Can't parse AST") # debug
511 return False
511 return False
512 else:
512 else:
513 if len(code_ast.body) == 1 and \
513 if len(code_ast.body) == 1 and \
514 not hasattr(code_ast.body[0], 'body'):
514 not hasattr(code_ast.body[0], 'body'):
515 #print("Simple statement") # debug
515 #print("Simple statement") # debug
516 return False
516 return False
517
517
518 # General fallback - accept more code
518 # General fallback - accept more code
519 return True
519 return True
520
520
521 def get_indent_spaces(self) -> int:
521 def get_indent_spaces(self) -> int:
522 sourcefor, n = self._indent_spaces_cache
522 sourcefor, n = self._indent_spaces_cache
523 if sourcefor == self.source:
523 if sourcefor == self.source:
524 assert n is not None
524 assert n is not None
525 return n
525 return n
526
526
527 # self.source always has a trailing newline
527 # self.source always has a trailing newline
528 n = find_next_indent(self.source[:-1])
528 n = find_next_indent(self.source[:-1])
529 self._indent_spaces_cache = (self.source, n)
529 self._indent_spaces_cache = (self.source, n)
530 return n
530 return n
531
531
532 # Backwards compatibility. I think all code that used .indent_spaces was
532 # Backwards compatibility. I think all code that used .indent_spaces was
533 # inside IPython, but we can leave this here until IPython 7 in case any
533 # inside IPython, but we can leave this here until IPython 7 in case any
534 # other modules are using it. -TK, November 2017
534 # other modules are using it. -TK, November 2017
535 indent_spaces = property(get_indent_spaces)
535 indent_spaces = property(get_indent_spaces)
536
536
537 def _store(self, lines, buffer=None, store='source'):
537 def _store(self, lines, buffer=None, store='source'):
538 """Store one or more lines of input.
538 """Store one or more lines of input.
539
539
540 If input lines are not newline-terminated, a newline is automatically
540 If input lines are not newline-terminated, a newline is automatically
541 appended."""
541 appended."""
542
542
543 if buffer is None:
543 if buffer is None:
544 buffer = self._buffer
544 buffer = self._buffer
545
545
546 if lines.endswith('\n'):
546 if lines.endswith('\n'):
547 buffer.append(lines)
547 buffer.append(lines)
548 else:
548 else:
549 buffer.append(lines+'\n')
549 buffer.append(lines+'\n')
550 setattr(self, store, self._set_source(buffer))
550 setattr(self, store, self._set_source(buffer))
551
551
552 def _set_source(self, buffer):
552 def _set_source(self, buffer):
553 return u''.join(buffer)
553 return u''.join(buffer)
554
554
555
555
556 class IPythonInputSplitter(InputSplitter):
556 class IPythonInputSplitter(InputSplitter):
557 """An input splitter that recognizes all of IPython's special syntax."""
557 """An input splitter that recognizes all of IPython's special syntax."""
558
558
559 # String with raw, untransformed input.
559 # String with raw, untransformed input.
560 source_raw = ''
560 source_raw = ''
561
561
562 # Flag to track when a transformer has stored input that it hasn't given
562 # Flag to track when a transformer has stored input that it hasn't given
563 # back yet.
563 # back yet.
564 transformer_accumulating = False
564 transformer_accumulating = False
565
565
566 # Flag to track when assemble_python_lines has stored input that it hasn't
566 # Flag to track when assemble_python_lines has stored input that it hasn't
567 # given back yet.
567 # given back yet.
568 within_python_line = False
568 within_python_line = False
569
569
570 # Private attributes
570 # Private attributes
571
571
572 # List with lines of raw input accumulated so far.
572 # List with lines of raw input accumulated so far.
573 _buffer_raw: List[str]
573 _buffer_raw: List[str]
574
574
575 def __init__(self, line_input_checker=True, physical_line_transforms=None,
575 def __init__(self, line_input_checker=True, physical_line_transforms=None,
576 logical_line_transforms=None, python_line_transforms=None):
576 logical_line_transforms=None, python_line_transforms=None):
577 super(IPythonInputSplitter, self).__init__()
577 super(IPythonInputSplitter, self).__init__()
578 self._buffer_raw = []
578 self._buffer_raw = []
579 self._validate = True
579 self._validate = True
580
580
581 if physical_line_transforms is not None:
581 if physical_line_transforms is not None:
582 self.physical_line_transforms = physical_line_transforms
582 self.physical_line_transforms = physical_line_transforms
583 else:
583 else:
584 self.physical_line_transforms = [
584 self.physical_line_transforms = [
585 leading_indent(),
585 leading_indent(),
586 classic_prompt(),
586 classic_prompt(),
587 ipy_prompt(),
587 ipy_prompt(),
588 cellmagic(end_on_blank_line=line_input_checker),
588 cellmagic(end_on_blank_line=line_input_checker),
589 ]
589 ]
590
590
591 self.assemble_logical_lines = assemble_logical_lines()
591 self.assemble_logical_lines = assemble_logical_lines()
592 if logical_line_transforms is not None:
592 if logical_line_transforms is not None:
593 self.logical_line_transforms = logical_line_transforms
593 self.logical_line_transforms = logical_line_transforms
594 else:
594 else:
595 self.logical_line_transforms = [
595 self.logical_line_transforms = [
596 help_end(),
596 help_end(),
597 escaped_commands(),
597 escaped_commands(),
598 assign_from_magic(),
598 assign_from_magic(),
599 assign_from_system(),
599 assign_from_system(),
600 ]
600 ]
601
601
602 self.assemble_python_lines = assemble_python_lines()
602 self.assemble_python_lines = assemble_python_lines()
603 if python_line_transforms is not None:
603 if python_line_transforms is not None:
604 self.python_line_transforms = python_line_transforms
604 self.python_line_transforms = python_line_transforms
605 else:
605 else:
606 # We don't use any of these at present
606 # We don't use any of these at present
607 self.python_line_transforms = []
607 self.python_line_transforms = []
608
608
609 @property
609 @property
610 def transforms(self):
610 def transforms(self):
611 "Quick access to all transformers."
611 "Quick access to all transformers."
612 return self.physical_line_transforms + \
612 return self.physical_line_transforms + \
613 [self.assemble_logical_lines] + self.logical_line_transforms + \
613 [self.assemble_logical_lines] + self.logical_line_transforms + \
614 [self.assemble_python_lines] + self.python_line_transforms
614 [self.assemble_python_lines] + self.python_line_transforms
615
615
616 @property
616 @property
617 def transforms_in_use(self):
617 def transforms_in_use(self):
618 """Transformers, excluding logical line transformers if we're in a
618 """Transformers, excluding logical line transformers if we're in a
619 Python line."""
619 Python line."""
620 t = self.physical_line_transforms[:]
620 t = self.physical_line_transforms[:]
621 if not self.within_python_line:
621 if not self.within_python_line:
622 t += [self.assemble_logical_lines] + self.logical_line_transforms
622 t += [self.assemble_logical_lines] + self.logical_line_transforms
623 return t + [self.assemble_python_lines] + self.python_line_transforms
623 return t + [self.assemble_python_lines] + self.python_line_transforms
624
624
625 def reset(self):
625 def reset(self):
626 """Reset the input buffer and associated state."""
626 """Reset the input buffer and associated state."""
627 super(IPythonInputSplitter, self).reset()
627 super(IPythonInputSplitter, self).reset()
628 self._buffer_raw[:] = []
628 self._buffer_raw[:] = []
629 self.source_raw = ''
629 self.source_raw = ''
630 self.transformer_accumulating = False
630 self.transformer_accumulating = False
631 self.within_python_line = False
631 self.within_python_line = False
632
632
633 for t in self.transforms:
633 for t in self.transforms:
634 try:
634 try:
635 t.reset()
635 t.reset()
636 except SyntaxError:
636 except SyntaxError:
637 # Nothing that calls reset() expects to handle transformer
637 # Nothing that calls reset() expects to handle transformer
638 # errors
638 # errors
639 pass
639 pass
640
640
641 def flush_transformers(self):
641 def flush_transformers(self):
642 def _flush(transform, outs):
642 def _flush(transform, outs):
643 """yield transformed lines
643 """yield transformed lines
644
644
645 always strings, never None
645 always strings, never None
646
646
647 transform: the current transform
647 transform: the current transform
648 outs: an iterable of previously transformed inputs.
648 outs: an iterable of previously transformed inputs.
649 Each may be multiline, which will be passed
649 Each may be multiline, which will be passed
650 one line at a time to transform.
650 one line at a time to transform.
651 """
651 """
652 for out in outs:
652 for out in outs:
653 for line in out.splitlines():
653 for line in out.splitlines():
654 # push one line at a time
654 # push one line at a time
655 tmp = transform.push(line)
655 tmp = transform.push(line)
656 if tmp is not None:
656 if tmp is not None:
657 yield tmp
657 yield tmp
658
658
659 # reset the transform
659 # reset the transform
660 tmp = transform.reset()
660 tmp = transform.reset()
661 if tmp is not None:
661 if tmp is not None:
662 yield tmp
662 yield tmp
663
663
664 out: List[str] = []
664 out: List[str] = []
665 for t in self.transforms_in_use:
665 for t in self.transforms_in_use:
666 out = _flush(t, out)
666 out = _flush(t, out)
667
667
668 out = list(out)
668 out = list(out)
669 if out:
669 if out:
670 self._store('\n'.join(out))
670 self._store('\n'.join(out))
671
671
672 def raw_reset(self):
672 def raw_reset(self):
673 """Return raw input only and perform a full reset.
673 """Return raw input only and perform a full reset.
674 """
674 """
675 out = self.source_raw
675 out = self.source_raw
676 self.reset()
676 self.reset()
677 return out
677 return out
678
678
679 def source_reset(self):
679 def source_reset(self):
680 try:
680 try:
681 self.flush_transformers()
681 self.flush_transformers()
682 return self.source
682 return self.source
683 finally:
683 finally:
684 self.reset()
684 self.reset()
685
685
686 def push_accepts_more(self):
686 def push_accepts_more(self):
687 if self.transformer_accumulating:
687 if self.transformer_accumulating:
688 return True
688 return True
689 else:
689 else:
690 return super(IPythonInputSplitter, self).push_accepts_more()
690 return super(IPythonInputSplitter, self).push_accepts_more()
691
691
692 def transform_cell(self, cell):
692 def transform_cell(self, cell):
693 """Process and translate a cell of input.
693 """Process and translate a cell of input.
694 """
694 """
695 self.reset()
695 self.reset()
696 try:
696 try:
697 self.push(cell)
697 self.push(cell)
698 self.flush_transformers()
698 self.flush_transformers()
699 return self.source
699 return self.source
700 finally:
700 finally:
701 self.reset()
701 self.reset()
702
702
703 def push(self, lines:str) -> bool:
703 def push(self, lines:str) -> bool:
704 """Push one or more lines of IPython input.
704 """Push one or more lines of IPython input.
705
705
706 This stores the given lines and returns a status code indicating
706 This stores the given lines and returns a status code indicating
707 whether the code forms a complete Python block or not, after processing
707 whether the code forms a complete Python block or not, after processing
708 all input lines for special IPython syntax.
708 all input lines for special IPython syntax.
709
709
710 Any exceptions generated in compilation are swallowed, but if an
710 Any exceptions generated in compilation are swallowed, but if an
711 exception was produced, the method returns True.
711 exception was produced, the method returns True.
712
712
713 Parameters
713 Parameters
714 ----------
714 ----------
715 lines : string
715 lines : string
716 One or more lines of Python input.
716 One or more lines of Python input.
717
717
718 Returns
718 Returns
719 -------
719 -------
720 is_complete : boolean
720 is_complete : boolean
721 True if the current input source (the result of the current input
721 True if the current input source (the result of the current input
722 plus prior inputs) forms a complete Python execution block. Note that
722 plus prior inputs) forms a complete Python execution block. Note that
723 this value is also stored as a private attribute (_is_complete), so it
723 this value is also stored as a private attribute (_is_complete), so it
724 can be queried at any time.
724 can be queried at any time.
725 """
725 """
726 assert isinstance(lines, str)
726 assert isinstance(lines, str)
727 # We must ensure all input is pure unicode
727 # We must ensure all input is pure unicode
728 # ''.splitlines() --> [], but we need to push the empty line to transformers
728 # ''.splitlines() --> [], but we need to push the empty line to transformers
729 lines_list = lines.splitlines()
729 lines_list = lines.splitlines()
730 if not lines_list:
730 if not lines_list:
731 lines_list = ['']
731 lines_list = ['']
732
732
733 # Store raw source before applying any transformations to it. Note
733 # Store raw source before applying any transformations to it. Note
734 # that this must be done *after* the reset() call that would otherwise
734 # that this must be done *after* the reset() call that would otherwise
735 # flush the buffer.
735 # flush the buffer.
736 self._store(lines, self._buffer_raw, 'source_raw')
736 self._store(lines, self._buffer_raw, 'source_raw')
737
737
738 transformed_lines_list = []
738 transformed_lines_list = []
739 for line in lines_list:
739 for line in lines_list:
740 transformed = self._transform_line(line)
740 transformed = self._transform_line(line)
741 if transformed is not None:
741 if transformed is not None:
742 transformed_lines_list.append(transformed)
742 transformed_lines_list.append(transformed)
743
743
744 if transformed_lines_list:
744 if transformed_lines_list:
745 transformed_lines = '\n'.join(transformed_lines_list)
745 transformed_lines = '\n'.join(transformed_lines_list)
746 return super(IPythonInputSplitter, self).push(transformed_lines)
746 return super(IPythonInputSplitter, self).push(transformed_lines)
747 else:
747 else:
748 # Got nothing back from transformers - they must be waiting for
748 # Got nothing back from transformers - they must be waiting for
749 # more input.
749 # more input.
750 return False
750 return False
751
751
752 def _transform_line(self, line):
752 def _transform_line(self, line):
753 """Push a line of input code through the various transformers.
753 """Push a line of input code through the various transformers.
754
754
755 Returns any output from the transformers, or None if a transformer
755 Returns any output from the transformers, or None if a transformer
756 is accumulating lines.
756 is accumulating lines.
757
757
758 Sets self.transformer_accumulating as a side effect.
758 Sets self.transformer_accumulating as a side effect.
759 """
759 """
760 def _accumulating(dbg):
760 def _accumulating(dbg):
761 #print(dbg)
761 #print(dbg)
762 self.transformer_accumulating = True
762 self.transformer_accumulating = True
763 return None
763 return None
764
764
765 for transformer in self.physical_line_transforms:
765 for transformer in self.physical_line_transforms:
766 line = transformer.push(line)
766 line = transformer.push(line)
767 if line is None:
767 if line is None:
768 return _accumulating(transformer)
768 return _accumulating(transformer)
769
769
770 if not self.within_python_line:
770 if not self.within_python_line:
771 line = self.assemble_logical_lines.push(line)
771 line = self.assemble_logical_lines.push(line)
772 if line is None:
772 if line is None:
773 return _accumulating('acc logical line')
773 return _accumulating('acc logical line')
774
774
775 for transformer in self.logical_line_transforms:
775 for transformer in self.logical_line_transforms:
776 line = transformer.push(line)
776 line = transformer.push(line)
777 if line is None:
777 if line is None:
778 return _accumulating(transformer)
778 return _accumulating(transformer)
779
779
780 line = self.assemble_python_lines.push(line)
780 line = self.assemble_python_lines.push(line)
781 if line is None:
781 if line is None:
782 self.within_python_line = True
782 self.within_python_line = True
783 return _accumulating('acc python line')
783 return _accumulating('acc python line')
784 else:
784 else:
785 self.within_python_line = False
785 self.within_python_line = False
786
786
787 for transformer in self.python_line_transforms:
787 for transformer in self.python_line_transforms:
788 line = transformer.push(line)
788 line = transformer.push(line)
789 if line is None:
789 if line is None:
790 return _accumulating(transformer)
790 return _accumulating(transformer)
791
791
792 #print("transformers clear") #debug
792 #print("transformers clear") #debug
793 self.transformer_accumulating = False
793 self.transformer_accumulating = False
794 return line
794 return line
795
795
@@ -1,539 +1,544 b''
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 from tokenize import untokenize, TokenError
12 from tokenize import untokenize, TokenError
13 from io import StringIO
13 from io import StringIO
14
14
15 from IPython.core.splitinput import LineInfo
15 from IPython.core.splitinput import LineInfo
16 from IPython.utils import tokenutil
16 from IPython.utils import tokenutil
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Globals
19 # Globals
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 # The escape sequences that define the syntax transformations IPython will
22 # The escape sequences that define the syntax transformations IPython will
23 # apply to user input. These can NOT be just changed here: many regular
23 # 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
24 # 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
25 # for all intents and purposes they constitute the 'IPython syntax', so they
26 # should be considered fixed.
26 # should be considered fixed.
27
27
28 ESC_SHELL = '!' # Send line to underlying system shell
28 ESC_SHELL = '!' # Send line to underlying system shell
29 ESC_SH_CAP = '!!' # Send line to system shell and capture output
29 ESC_SH_CAP = '!!' # Send line to system shell and capture output
30 ESC_HELP = '?' # Find information about object
30 ESC_HELP = '?' # Find information about object
31 ESC_HELP2 = '??' # Find extra-detailed information about object
31 ESC_HELP2 = '??' # Find extra-detailed information about object
32 ESC_MAGIC = '%' # Call magic function
32 ESC_MAGIC = '%' # Call magic function
33 ESC_MAGIC2 = '%%' # Call cell-magic function
33 ESC_MAGIC2 = '%%' # Call cell-magic function
34 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
34 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
35 ESC_QUOTE2 = ';' # Quote all args as a single string, call
35 ESC_QUOTE2 = ';' # Quote all args as a single string, call
36 ESC_PAREN = '/' # Call first argument with rest of line as arguments
36 ESC_PAREN = '/' # Call first argument with rest of line as arguments
37
37
38 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
38 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
39 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
39 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
40 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
40 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
41
41
42
42
43 class InputTransformer(metaclass=abc.ABCMeta):
43 class InputTransformer(metaclass=abc.ABCMeta):
44 """Abstract base class for line-based input transformers."""
44 """Abstract base class for line-based input transformers."""
45
45
46 @abc.abstractmethod
46 @abc.abstractmethod
47 def push(self, line):
47 def push(self, line):
48 """Send a line of input to the transformer, returning the transformed
48 """Send a line of input to the transformer, returning the transformed
49 input or None if the transformer is waiting for more input.
49 input or None if the transformer is waiting for more input.
50
50
51 Must be overridden by subclasses.
51 Must be overridden by subclasses.
52
52
53 Implementations may raise ``SyntaxError`` if the input is invalid. No
53 Implementations may raise ``SyntaxError`` if the input is invalid. No
54 other exceptions may be raised.
54 other exceptions may be raised.
55 """
55 """
56 pass
56 pass
57
57
58 @abc.abstractmethod
58 @abc.abstractmethod
59 def reset(self):
59 def reset(self):
60 """Return, transformed any lines that the transformer has accumulated,
60 """Return, transformed any lines that the transformer has accumulated,
61 and reset its internal state.
61 and reset its internal state.
62
62
63 Must be overridden by subclasses.
63 Must be overridden by subclasses.
64 """
64 """
65 pass
65 pass
66
66
67 @classmethod
67 @classmethod
68 def wrap(cls, func):
68 def wrap(cls, func):
69 """Can be used by subclasses as a decorator, to return a factory that
69 """Can be used by subclasses as a decorator, to return a factory that
70 will allow instantiation with the decorated object.
70 will allow instantiation with the decorated object.
71 """
71 """
72 @functools.wraps(func)
72 @functools.wraps(func)
73 def transformer_factory(**kwargs):
73 def transformer_factory(**kwargs):
74 return cls(func, **kwargs) # type: ignore [call-arg]
74 return cls(func, **kwargs) # type: ignore [call-arg]
75
75
76 return transformer_factory
76 return transformer_factory
77
77
78 class StatelessInputTransformer(InputTransformer):
78 class StatelessInputTransformer(InputTransformer):
79 """Wrapper for a stateless input transformer implemented as a function."""
79 """Wrapper for a stateless input transformer implemented as a function."""
80 def __init__(self, func):
80 def __init__(self, func):
81 self.func = func
81 self.func = func
82
82
83 def __repr__(self):
83 def __repr__(self):
84 return "StatelessInputTransformer(func={0!r})".format(self.func)
84 return "StatelessInputTransformer(func={0!r})".format(self.func)
85
85
86 def push(self, line):
86 def push(self, line):
87 """Send a line of input to the transformer, returning the
87 """Send a line of input to the transformer, returning the
88 transformed input."""
88 transformed input."""
89 return self.func(line)
89 return self.func(line)
90
90
91 def reset(self):
91 def reset(self):
92 """No-op - exists for compatibility."""
92 """No-op - exists for compatibility."""
93 pass
93 pass
94
94
95 class CoroutineInputTransformer(InputTransformer):
95 class CoroutineInputTransformer(InputTransformer):
96 """Wrapper for an input transformer implemented as a coroutine."""
96 """Wrapper for an input transformer implemented as a coroutine."""
97 def __init__(self, coro, **kwargs):
97 def __init__(self, coro, **kwargs):
98 # Prime it
98 # Prime it
99 self.coro = coro(**kwargs)
99 self.coro = coro(**kwargs)
100 next(self.coro)
100 next(self.coro)
101
101
102 def __repr__(self):
102 def __repr__(self):
103 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
103 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
104
104
105 def push(self, line):
105 def push(self, line):
106 """Send a line of input to the transformer, returning the
106 """Send a line of input to the transformer, returning the
107 transformed input or None if the transformer is waiting for more
107 transformed input or None if the transformer is waiting for more
108 input.
108 input.
109 """
109 """
110 return self.coro.send(line)
110 return self.coro.send(line)
111
111
112 def reset(self):
112 def reset(self):
113 """Return, transformed any lines that the transformer has
113 """Return, transformed any lines that the transformer has
114 accumulated, and reset its internal state.
114 accumulated, and reset its internal state.
115 """
115 """
116 return self.coro.send(None)
116 return self.coro.send(None)
117
117
118 class TokenInputTransformer(InputTransformer):
118 class TokenInputTransformer(InputTransformer):
119 """Wrapper for a token-based input transformer.
119 """Wrapper for a token-based input transformer.
120
120
121 func should accept a list of tokens (5-tuples, see tokenize docs), and
121 func should accept a list of tokens (5-tuples, see tokenize docs), and
122 return an iterable which can be passed to tokenize.untokenize().
122 return an iterable which can be passed to tokenize.untokenize().
123 """
123 """
124 def __init__(self, func):
124 def __init__(self, func):
125 self.func = func
125 self.func = func
126 self.buf = []
126 self.buf = []
127 self.reset_tokenizer()
127 self.reset_tokenizer()
128
128
129 def reset_tokenizer(self):
129 def reset_tokenizer(self):
130 it = iter(self.buf)
130 it = iter(self.buf)
131 self.tokenizer = tokenutil.generate_tokens_catch_errors(it.__next__)
131 self.tokenizer = tokenutil.generate_tokens_catch_errors(it.__next__)
132
132
133 def push(self, line):
133 def push(self, line):
134 self.buf.append(line + '\n')
134 self.buf.append(line + '\n')
135 if all(l.isspace() for l in self.buf):
135 if all(l.isspace() for l in self.buf):
136 return self.reset()
136 return self.reset()
137
137
138 tokens = []
138 tokens = []
139 stop_at_NL = False
139 stop_at_NL = False
140 try:
140 try:
141 for intok in self.tokenizer:
141 for intok in self.tokenizer:
142 tokens.append(intok)
142 tokens.append(intok)
143 t = intok[0]
143 t = intok[0]
144 if t == tokenize.NEWLINE or (stop_at_NL and t == tokenize.NL):
144 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
145 # Stop before we try to pull a line we don't have yet
146 break
146 break
147 elif t == tokenize.ERRORTOKEN:
147 elif t == tokenize.ERRORTOKEN:
148 stop_at_NL = True
148 stop_at_NL = True
149 except TokenError:
149 except TokenError:
150 # Multi-line statement - stop and try again with the next line
150 # Multi-line statement - stop and try again with the next line
151 self.reset_tokenizer()
151 self.reset_tokenizer()
152 return None
152 return None
153
153
154 return self.output(tokens)
154 return self.output(tokens)
155
155
156 def output(self, tokens):
156 def output(self, tokens):
157 self.buf.clear()
157 self.buf.clear()
158 self.reset_tokenizer()
158 self.reset_tokenizer()
159 return untokenize(self.func(tokens)).rstrip('\n')
159 return untokenize(self.func(tokens)).rstrip('\n')
160
160
161 def reset(self):
161 def reset(self):
162 l = ''.join(self.buf)
162 l = ''.join(self.buf)
163 self.buf.clear()
163 self.buf.clear()
164 self.reset_tokenizer()
164 self.reset_tokenizer()
165 if l:
165 if l:
166 return l.rstrip('\n')
166 return l.rstrip('\n')
167
167
168 class assemble_python_lines(TokenInputTransformer):
168 class assemble_python_lines(TokenInputTransformer):
169 def __init__(self):
169 def __init__(self):
170 super(assemble_python_lines, self).__init__(None)
170 super(assemble_python_lines, self).__init__(None)
171
171
172 def output(self, tokens):
172 def output(self, tokens):
173 return self.reset()
173 return self.reset()
174
174
175 @CoroutineInputTransformer.wrap
175 @CoroutineInputTransformer.wrap
176 def assemble_logical_lines():
176 def assemble_logical_lines():
177 r"""Join lines following explicit line continuations (\)"""
177 r"""Join lines following explicit line continuations (\)"""
178 line = ''
178 line = ''
179 while True:
179 while True:
180 line = (yield line)
180 line = (yield line)
181 if not line or line.isspace():
181 if not line or line.isspace():
182 continue
182 continue
183
183
184 parts = []
184 parts = []
185 while line is not None:
185 while line is not None:
186 if line.endswith('\\') and (not has_comment(line)):
186 if line.endswith('\\') and (not has_comment(line)):
187 parts.append(line[:-1])
187 parts.append(line[:-1])
188 line = (yield None) # Get another line
188 line = (yield None) # Get another line
189 else:
189 else:
190 parts.append(line)
190 parts.append(line)
191 break
191 break
192
192
193 # Output
193 # Output
194 line = ''.join(parts)
194 line = ''.join(parts)
195
195
196 # Utilities
196 # Utilities
197 def _make_help_call(target:str, esc:str, lspace:str) -> str:
197 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
198 """Prepares a pinfo(2)/psearch call from a target name and the escape
199 (i.e. ? or ??)"""
199 (i.e. ? or ??)"""
200 method = 'pinfo2' if esc == '??' \
200 method = 'pinfo2' if esc == '??' \
201 else 'psearch' if '*' in target \
201 else 'psearch' if '*' in target \
202 else 'pinfo'
202 else 'pinfo'
203 arg = " ".join([method, target])
203 arg = " ".join([method, target])
204 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
204 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
205 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
205 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
206 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
206 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
207 return "%sget_ipython().run_line_magic(%r, %r)" % (
207 return "%sget_ipython().run_line_magic(%r, %r)" % (
208 lspace,
208 lspace,
209 t_magic_name,
209 t_magic_name,
210 t_magic_arg_s,
210 t_magic_arg_s,
211 )
211 )
212
212
213
213
214 # These define the transformations for the different escape characters.
214 # These define the transformations for the different escape characters.
215 def _tr_system(line_info:LineInfo):
215 def _tr_system(line_info: LineInfo):
216 "Translate lines escaped with: !"
216 "Translate lines escaped with: !"
217 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
217 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
218 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
218 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
219
219
220 def _tr_system2(line_info:LineInfo):
220
221 def _tr_system2(line_info: LineInfo):
221 "Translate lines escaped with: !!"
222 "Translate lines escaped with: !!"
222 cmd = line_info.line.lstrip()[2:]
223 cmd = line_info.line.lstrip()[2:]
223 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
224 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
224
225
225 def _tr_help(line_info:LineInfo):
226
227 def _tr_help(line_info: LineInfo):
226 "Translate lines escaped with: ?/??"
228 "Translate lines escaped with: ?/??"
227 # A naked help line should just fire the intro help screen
229 # A naked help line should just fire the intro help screen
228 if not line_info.line[1:]:
230 if not line_info.line[1:]:
229 return 'get_ipython().show_usage()'
231 return 'get_ipython().show_usage()'
230
232
231 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
233 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
232
234
233 def _tr_magic(line_info:LineInfo):
235
236 def _tr_magic(line_info: LineInfo):
234 "Translate lines escaped with: %"
237 "Translate lines escaped with: %"
235 tpl = '%sget_ipython().run_line_magic(%r, %r)'
238 tpl = '%sget_ipython().run_line_magic(%r, %r)'
236 if line_info.line.startswith(ESC_MAGIC2):
239 if line_info.line.startswith(ESC_MAGIC2):
237 return line_info.line
240 return line_info.line
238 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
241 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
239 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
242 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
240 t_magic_name, _, t_magic_arg_s = cmd.partition(' ')
243 t_magic_name, _, t_magic_arg_s = cmd.partition(' ')
241 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
244 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
242 return tpl % (line_info.pre, t_magic_name, t_magic_arg_s)
245 return tpl % (line_info.pre, t_magic_name, t_magic_arg_s)
243
246
244 def _tr_quote(line_info:LineInfo):
247
248 def _tr_quote(line_info: LineInfo):
245 "Translate lines escaped with: ,"
249 "Translate lines escaped with: ,"
246 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
250 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
247 '", "'.join(line_info.the_rest.split()) )
251 '", "'.join(line_info.the_rest.split()) )
248
252
249 def _tr_quote2(line_info:LineInfo):
253
254 def _tr_quote2(line_info: LineInfo):
250 "Translate lines escaped with: ;"
255 "Translate lines escaped with: ;"
251 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
256 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
252 line_info.the_rest)
257 line_info.the_rest)
253
258
254 def _tr_paren(line_info:LineInfo):
259
260 def _tr_paren(line_info: LineInfo):
255 "Translate lines escaped with: /"
261 "Translate lines escaped with: /"
256 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
262 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
257 ", ".join(line_info.the_rest.split()))
263 ", ".join(line_info.the_rest.split()))
258
264
259 tr = { ESC_SHELL : _tr_system,
265 tr = { ESC_SHELL : _tr_system,
260 ESC_SH_CAP : _tr_system2,
266 ESC_SH_CAP : _tr_system2,
261 ESC_HELP : _tr_help,
267 ESC_HELP : _tr_help,
262 ESC_HELP2 : _tr_help,
268 ESC_HELP2 : _tr_help,
263 ESC_MAGIC : _tr_magic,
269 ESC_MAGIC : _tr_magic,
264 ESC_QUOTE : _tr_quote,
270 ESC_QUOTE : _tr_quote,
265 ESC_QUOTE2 : _tr_quote2,
271 ESC_QUOTE2 : _tr_quote2,
266 ESC_PAREN : _tr_paren }
272 ESC_PAREN : _tr_paren }
267
273
268 @StatelessInputTransformer.wrap
274 @StatelessInputTransformer.wrap
269 def escaped_commands(line:str):
275 def escaped_commands(line: str):
270 """Transform escaped commands - %magic, !system, ?help + various autocalls.
276 """Transform escaped commands - %magic, !system, ?help + various autocalls."""
271 """
272 if not line or line.isspace():
277 if not line or line.isspace():
273 return line
278 return line
274 lineinf = LineInfo(line)
279 lineinf = LineInfo(line)
275 if lineinf.esc not in tr:
280 if lineinf.esc not in tr:
276 return line
281 return line
277
282
278 return tr[lineinf.esc](lineinf)
283 return tr[lineinf.esc](lineinf)
279
284
280 _initial_space_re = re.compile(r'\s*')
285 _initial_space_re = re.compile(r'\s*')
281
286
282 _help_end_re = re.compile(r"""(%{0,2}
287 _help_end_re = re.compile(r"""(%{0,2}
283 (?!\d)[\w*]+ # Variable name
288 (?!\d)[\w*]+ # Variable name
284 (\.(?!\d)[\w*]+)* # .etc.etc
289 (\.(?!\d)[\w*]+)* # .etc.etc
285 )
290 )
286 (\?\??)$ # ? or ??
291 (\?\??)$ # ? or ??
287 """,
292 """,
288 re.VERBOSE)
293 re.VERBOSE)
289
294
290 # Extra pseudotokens for multiline strings and data structures
295 # Extra pseudotokens for multiline strings and data structures
291 _MULTILINE_STRING = object()
296 _MULTILINE_STRING = object()
292 _MULTILINE_STRUCTURE = object()
297 _MULTILINE_STRUCTURE = object()
293
298
294 def _line_tokens(line):
299 def _line_tokens(line):
295 """Helper for has_comment and ends_in_comment_or_string."""
300 """Helper for has_comment and ends_in_comment_or_string."""
296 readline = StringIO(line).readline
301 readline = StringIO(line).readline
297 toktypes = set()
302 toktypes = set()
298 try:
303 try:
299 for t in tokenutil.generate_tokens_catch_errors(readline):
304 for t in tokenutil.generate_tokens_catch_errors(readline):
300 toktypes.add(t[0])
305 toktypes.add(t[0])
301 except TokenError as e:
306 except TokenError as e:
302 # There are only two cases where a TokenError is raised.
307 # There are only two cases where a TokenError is raised.
303 if 'multi-line string' in e.args[0]:
308 if 'multi-line string' in e.args[0]:
304 toktypes.add(_MULTILINE_STRING)
309 toktypes.add(_MULTILINE_STRING)
305 else:
310 else:
306 toktypes.add(_MULTILINE_STRUCTURE)
311 toktypes.add(_MULTILINE_STRUCTURE)
307 return toktypes
312 return toktypes
308
313
309 def has_comment(src):
314 def has_comment(src):
310 """Indicate whether an input line has (i.e. ends in, or is) a comment.
315 """Indicate whether an input line has (i.e. ends in, or is) a comment.
311
316
312 This uses tokenize, so it can distinguish comments from # inside strings.
317 This uses tokenize, so it can distinguish comments from # inside strings.
313
318
314 Parameters
319 Parameters
315 ----------
320 ----------
316 src : string
321 src : string
317 A single line input string.
322 A single line input string.
318
323
319 Returns
324 Returns
320 -------
325 -------
321 comment : bool
326 comment : bool
322 True if source has a comment.
327 True if source has a comment.
323 """
328 """
324 return (tokenize.COMMENT in _line_tokens(src))
329 return (tokenize.COMMENT in _line_tokens(src))
325
330
326 def ends_in_comment_or_string(src):
331 def ends_in_comment_or_string(src):
327 """Indicates whether or not an input line ends in a comment or within
332 """Indicates whether or not an input line ends in a comment or within
328 a multiline string.
333 a multiline string.
329
334
330 Parameters
335 Parameters
331 ----------
336 ----------
332 src : string
337 src : string
333 A single line input string.
338 A single line input string.
334
339
335 Returns
340 Returns
336 -------
341 -------
337 comment : bool
342 comment : bool
338 True if source ends in a comment or multiline string.
343 True if source ends in a comment or multiline string.
339 """
344 """
340 toktypes = _line_tokens(src)
345 toktypes = _line_tokens(src)
341 return (tokenize.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
346 return (tokenize.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
342
347
343
348
344 @StatelessInputTransformer.wrap
349 @StatelessInputTransformer.wrap
345 def help_end(line:str):
350 def help_end(line: str):
346 """Translate lines with ?/?? at the end"""
351 """Translate lines with ?/?? at the end"""
347 m = _help_end_re.search(line)
352 m = _help_end_re.search(line)
348 if m is None or ends_in_comment_or_string(line):
353 if m is None or ends_in_comment_or_string(line):
349 return line
354 return line
350 target = m.group(1)
355 target = m.group(1)
351 esc = m.group(3)
356 esc = m.group(3)
352 match = _initial_space_re.match(line)
357 match = _initial_space_re.match(line)
353 assert match is not None
358 assert match is not None
354 lspace = match.group(0)
359 lspace = match.group(0)
355
360
356 return _make_help_call(target, esc, lspace)
361 return _make_help_call(target, esc, lspace)
357
362
358
363
359 @CoroutineInputTransformer.wrap
364 @CoroutineInputTransformer.wrap
360 def cellmagic(end_on_blank_line:bool=False):
365 def cellmagic(end_on_blank_line: bool = False):
361 """Captures & transforms cell magics.
366 """Captures & transforms cell magics.
362
367
363 After a cell magic is started, this stores up any lines it gets until it is
368 After a cell magic is started, this stores up any lines it gets until it is
364 reset (sent None).
369 reset (sent None).
365 """
370 """
366 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
371 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
367 cellmagic_help_re = re.compile(r'%%\w+\?')
372 cellmagic_help_re = re.compile(r'%%\w+\?')
368 line = ''
373 line = ''
369 while True:
374 while True:
370 line = (yield line)
375 line = (yield line)
371 # consume leading empty lines
376 # consume leading empty lines
372 while not line:
377 while not line:
373 line = (yield line)
378 line = (yield line)
374
379
375 if not line.startswith(ESC_MAGIC2):
380 if not line.startswith(ESC_MAGIC2):
376 # This isn't a cell magic, idle waiting for reset then start over
381 # This isn't a cell magic, idle waiting for reset then start over
377 while line is not None:
382 while line is not None:
378 line = (yield line)
383 line = (yield line)
379 continue
384 continue
380
385
381 if cellmagic_help_re.match(line):
386 if cellmagic_help_re.match(line):
382 # This case will be handled by help_end
387 # This case will be handled by help_end
383 continue
388 continue
384
389
385 first = line
390 first = line
386 body = []
391 body = []
387 line = (yield None)
392 line = (yield None)
388 while (line is not None) and \
393 while (line is not None) and \
389 ((line.strip() != '') or not end_on_blank_line):
394 ((line.strip() != '') or not end_on_blank_line):
390 body.append(line)
395 body.append(line)
391 line = (yield None)
396 line = (yield None)
392
397
393 # Output
398 # Output
394 magic_name, _, first = first.partition(' ')
399 magic_name, _, first = first.partition(' ')
395 magic_name = magic_name.lstrip(ESC_MAGIC2)
400 magic_name = magic_name.lstrip(ESC_MAGIC2)
396 line = tpl % (magic_name, first, u'\n'.join(body))
401 line = tpl % (magic_name, first, u'\n'.join(body))
397
402
398
403
399 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
404 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
400 """Remove matching input prompts from a block of input.
405 """Remove matching input prompts from a block of input.
401
406
402 Parameters
407 Parameters
403 ----------
408 ----------
404 prompt_re : regular expression
409 prompt_re : regular expression
405 A regular expression matching any input prompt (including continuation)
410 A regular expression matching any input prompt (including continuation)
406 initial_re : regular expression, optional
411 initial_re : regular expression, optional
407 A regular expression matching only the initial prompt, but not continuation.
412 A regular expression matching only the initial prompt, but not continuation.
408 If no initial expression is given, prompt_re will be used everywhere.
413 If no initial expression is given, prompt_re will be used everywhere.
409 Used mainly for plain Python prompts, where the continuation prompt
414 Used mainly for plain Python prompts, where the continuation prompt
410 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
415 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
411
416
412 Notes
417 Notes
413 -----
418 -----
414 If `initial_re` and `prompt_re differ`,
419 If `initial_re` and `prompt_re differ`,
415 only `initial_re` will be tested against the first line.
420 only `initial_re` will be tested against the first line.
416 If any prompt is found on the first two lines,
421 If any prompt is found on the first two lines,
417 prompts will be stripped from the rest of the block.
422 prompts will be stripped from the rest of the block.
418 """
423 """
419 if initial_re is None:
424 if initial_re is None:
420 initial_re = prompt_re
425 initial_re = prompt_re
421 line = ''
426 line = ''
422 while True:
427 while True:
423 line = (yield line)
428 line = (yield line)
424
429
425 # First line of cell
430 # First line of cell
426 if line is None:
431 if line is None:
427 continue
432 continue
428 out, n1 = initial_re.subn('', line, count=1)
433 out, n1 = initial_re.subn('', line, count=1)
429 if turnoff_re and not n1:
434 if turnoff_re and not n1:
430 if turnoff_re.match(line):
435 if turnoff_re.match(line):
431 # We're in e.g. a cell magic; disable this transformer for
436 # We're in e.g. a cell magic; disable this transformer for
432 # the rest of the cell.
437 # the rest of the cell.
433 while line is not None:
438 while line is not None:
434 line = (yield line)
439 line = (yield line)
435 continue
440 continue
436
441
437 line = (yield out)
442 line = (yield out)
438
443
439 if line is None:
444 if line is None:
440 continue
445 continue
441 # check for any prompt on the second line of the cell,
446 # check for any prompt on the second line of the cell,
442 # because people often copy from just after the first prompt,
447 # because people often copy from just after the first prompt,
443 # so we might not see it in the first line.
448 # so we might not see it in the first line.
444 out, n2 = prompt_re.subn('', line, count=1)
449 out, n2 = prompt_re.subn('', line, count=1)
445 line = (yield out)
450 line = (yield out)
446
451
447 if n1 or n2:
452 if n1 or n2:
448 # Found a prompt in the first two lines - check for it in
453 # Found a prompt in the first two lines - check for it in
449 # the rest of the cell as well.
454 # the rest of the cell as well.
450 while line is not None:
455 while line is not None:
451 line = (yield prompt_re.sub('', line, count=1))
456 line = (yield prompt_re.sub('', line, count=1))
452
457
453 else:
458 else:
454 # Prompts not in input - wait for reset
459 # Prompts not in input - wait for reset
455 while line is not None:
460 while line is not None:
456 line = (yield line)
461 line = (yield line)
457
462
458 @CoroutineInputTransformer.wrap
463 @CoroutineInputTransformer.wrap
459 def classic_prompt():
464 def classic_prompt():
460 """Strip the >>>/... prompts of the Python interactive shell."""
465 """Strip the >>>/... prompts of the Python interactive shell."""
461 # FIXME: non-capturing version (?:...) usable?
466 # FIXME: non-capturing version (?:...) usable?
462 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
467 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
463 initial_re = re.compile(r'^>>>( |$)')
468 initial_re = re.compile(r'^>>>( |$)')
464 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
469 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
465 turnoff_re = re.compile(r'^[%!]')
470 turnoff_re = re.compile(r'^[%!]')
466 return _strip_prompts(prompt_re, initial_re, turnoff_re)
471 return _strip_prompts(prompt_re, initial_re, turnoff_re)
467
472
468 @CoroutineInputTransformer.wrap
473 @CoroutineInputTransformer.wrap
469 def ipy_prompt():
474 def ipy_prompt():
470 """Strip IPython's In [1]:/...: prompts."""
475 """Strip IPython's In [1]:/...: prompts."""
471 # FIXME: non-capturing version (?:...) usable?
476 # FIXME: non-capturing version (?:...) usable?
472 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
477 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
473 # Disable prompt stripping inside cell magics
478 # Disable prompt stripping inside cell magics
474 turnoff_re = re.compile(r'^%%')
479 turnoff_re = re.compile(r'^%%')
475 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
480 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
476
481
477
482
478 @CoroutineInputTransformer.wrap
483 @CoroutineInputTransformer.wrap
479 def leading_indent():
484 def leading_indent():
480 """Remove leading indentation.
485 """Remove leading indentation.
481
486
482 If the first line starts with a spaces or tabs, the same whitespace will be
487 If the first line starts with a spaces or tabs, the same whitespace will be
483 removed from each following line until it is reset.
488 removed from each following line until it is reset.
484 """
489 """
485 space_re = re.compile(r'^[ \t]+')
490 space_re = re.compile(r'^[ \t]+')
486 line = ''
491 line = ''
487 while True:
492 while True:
488 line = (yield line)
493 line = (yield line)
489
494
490 if line is None:
495 if line is None:
491 continue
496 continue
492
497
493 m = space_re.match(line)
498 m = space_re.match(line)
494 if m:
499 if m:
495 space = m.group(0)
500 space = m.group(0)
496 while line is not None:
501 while line is not None:
497 if line.startswith(space):
502 if line.startswith(space):
498 line = line[len(space):]
503 line = line[len(space):]
499 line = (yield line)
504 line = (yield line)
500 else:
505 else:
501 # No leading spaces - wait for reset
506 # No leading spaces - wait for reset
502 while line is not None:
507 while line is not None:
503 line = (yield line)
508 line = (yield line)
504
509
505
510
506 _assign_pat = \
511 _assign_pat = \
507 r'''(?P<lhs>(\s*)
512 r'''(?P<lhs>(\s*)
508 ([\w\.]+) # Initial identifier
513 ([\w\.]+) # Initial identifier
509 (\s*,\s*
514 (\s*,\s*
510 \*?[\w\.]+)* # Further identifiers for unpacking
515 \*?[\w\.]+)* # Further identifiers for unpacking
511 \s*?,? # Trailing comma
516 \s*?,? # Trailing comma
512 )
517 )
513 \s*=\s*
518 \s*=\s*
514 '''
519 '''
515
520
516 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
521 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
517 assign_system_template = '%s = get_ipython().getoutput(%r)'
522 assign_system_template = '%s = get_ipython().getoutput(%r)'
518 @StatelessInputTransformer.wrap
523 @StatelessInputTransformer.wrap
519 def assign_from_system(line):
524 def assign_from_system(line):
520 """Transform assignment from system commands (e.g. files = !ls)"""
525 """Transform assignment from system commands (e.g. files = !ls)"""
521 m = assign_system_re.match(line)
526 m = assign_system_re.match(line)
522 if m is None:
527 if m is None:
523 return line
528 return line
524
529
525 return assign_system_template % m.group('lhs', 'cmd')
530 return assign_system_template % m.group('lhs', 'cmd')
526
531
527 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
532 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
528 assign_magic_template = '%s = get_ipython().run_line_magic(%r, %r)'
533 assign_magic_template = '%s = get_ipython().run_line_magic(%r, %r)'
529 @StatelessInputTransformer.wrap
534 @StatelessInputTransformer.wrap
530 def assign_from_magic(line):
535 def assign_from_magic(line):
531 """Transform assignment from magic commands (e.g. a = %who_ls)"""
536 """Transform assignment from magic commands (e.g. a = %who_ls)"""
532 m = assign_magic_re.match(line)
537 m = assign_magic_re.match(line)
533 if m is None:
538 if m is None:
534 return line
539 return line
535 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
540 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
536 m_lhs, m_cmd = m.group('lhs', 'cmd')
541 m_lhs, m_cmd = m.group('lhs', 'cmd')
537 t_magic_name, _, t_magic_arg_s = m_cmd.partition(' ')
542 t_magic_name, _, t_magic_arg_s = m_cmd.partition(' ')
538 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
543 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
539 return assign_magic_template % (m_lhs, t_magic_name, t_magic_arg_s)
544 return assign_magic_template % (m_lhs, t_magic_name, t_magic_arg_s)
@@ -1,67 +1,68 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Compatibility tricks for Python 3. Mainly to do with unicode.
2 """Compatibility tricks for Python 3. Mainly to do with unicode.
3
3
4 This file is deprecated and will be removed in a future version.
4 This file is deprecated and will be removed in a future version.
5 """
5 """
6 import platform
6 import platform
7 import builtins as builtin_mod
7 import builtins as builtin_mod
8
8
9 from .encoding import DEFAULT_ENCODING
9 from .encoding import DEFAULT_ENCODING
10
10
11
11
12 def decode(s, encoding=None):
12 def decode(s, encoding=None):
13 encoding = encoding or DEFAULT_ENCODING
13 encoding = encoding or DEFAULT_ENCODING
14 return s.decode(encoding, "replace")
14 return s.decode(encoding, "replace")
15
15
16
16
17 def encode(u, encoding=None):
17 def encode(u, encoding=None):
18 encoding = encoding or DEFAULT_ENCODING
18 encoding = encoding or DEFAULT_ENCODING
19 return u.encode(encoding, "replace")
19 return u.encode(encoding, "replace")
20
20
21
21
22 def cast_unicode(s, encoding=None):
22 def cast_unicode(s, encoding=None):
23 if isinstance(s, bytes):
23 if isinstance(s, bytes):
24 return decode(s, encoding)
24 return decode(s, encoding)
25 return s
25 return s
26
26
27
27
28 def safe_unicode(e):
28 def safe_unicode(e):
29 """unicode(e) with various fallbacks. Used for exceptions, which may not be
29 """unicode(e) with various fallbacks. Used for exceptions, which may not be
30 safe to call unicode() on.
30 safe to call unicode() on.
31 """
31 """
32 try:
32 try:
33 return str(e)
33 return str(e)
34 except UnicodeError:
34 except UnicodeError:
35 pass
35 pass
36
36
37 try:
37 try:
38 return repr(e)
38 return repr(e)
39 except UnicodeError:
39 except UnicodeError:
40 pass
40 pass
41
41
42 return "Unrecoverably corrupt evalue"
42 return "Unrecoverably corrupt evalue"
43
43
44
44
45 # keep reference to builtin_mod because the kernel overrides that value
45 # keep reference to builtin_mod because the kernel overrides that value
46 # to forward requests to a frontend.
46 # to forward requests to a frontend.
47 def input(prompt=""):
47 def input(prompt=""):
48 return builtin_mod.input(prompt)
48 return builtin_mod.input(prompt)
49
49
50
50
51 def execfile(fname, glob, loc=None, compiler=None):
51 def execfile(fname, glob, loc=None, compiler=None):
52 loc = loc if (loc is not None) else glob
52 loc = loc if (loc is not None) else glob
53 with open(fname, "rb") as f:
53 with open(fname, "rb") as f:
54 compiler = compiler or compile
54 compiler = compiler or compile
55 exec(compiler(f.read(), fname, "exec"), glob, loc)
55 exec(compiler(f.read(), fname, "exec"), glob, loc)
56
56
57
57
58 PYPY = platform.python_implementation() == "PyPy"
58 PYPY = platform.python_implementation() == "PyPy"
59
59
60
60 # Cython still rely on that as a Dec 28 2019
61 # Cython still rely on that as a Dec 28 2019
61 # See https://github.com/cython/cython/pull/3291 and
62 # See https://github.com/cython/cython/pull/3291 and
62 # https://github.com/ipython/ipython/issues/12068
63 # https://github.com/ipython/ipython/issues/12068
63 def no_code(x, encoding=None):
64 def no_code(x, encoding=None):
64 return x
65 return x
65
66
66
67
67 unicode_to_str = cast_bytes_py2 = no_code
68 unicode_to_str = cast_bytes_py2 = no_code
@@ -1,785 +1,799 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with strings and text.
3 Utilities for working with strings and text.
4
4
5 Inheritance diagram:
5 Inheritance diagram:
6
6
7 .. inheritance-diagram:: IPython.utils.text
7 .. inheritance-diagram:: IPython.utils.text
8 :parts: 3
8 :parts: 3
9 """
9 """
10
10
11 import os
11 import os
12 import re
12 import re
13 import string
13 import string
14 import sys
14 import sys
15 import textwrap
15 import textwrap
16 import warnings
16 import warnings
17 from string import Formatter
17 from string import Formatter
18 from pathlib import Path
18 from pathlib import Path
19
19
20 from typing import List, Union, Optional, Dict, Tuple
20 from typing import List, Union, Optional, Dict, Tuple
21
21
22
22
23 class LSString(str):
23 class LSString(str):
24 """String derivative with a special access attributes.
24 """String derivative with a special access attributes.
25
25
26 These are normal strings, but with the special attributes:
26 These are normal strings, but with the special attributes:
27
27
28 .l (or .list) : value as list (split on newlines).
28 .l (or .list) : value as list (split on newlines).
29 .n (or .nlstr): original value (the string itself).
29 .n (or .nlstr): original value (the string itself).
30 .s (or .spstr): value as whitespace-separated string.
30 .s (or .spstr): value as whitespace-separated string.
31 .p (or .paths): list of path objects (requires path.py package)
31 .p (or .paths): list of path objects (requires path.py package)
32
32
33 Any values which require transformations are computed only once and
33 Any values which require transformations are computed only once and
34 cached.
34 cached.
35
35
36 Such strings are very useful to efficiently interact with the shell, which
36 Such strings are very useful to efficiently interact with the shell, which
37 typically only understands whitespace-separated options for commands."""
37 typically only understands whitespace-separated options for commands."""
38
38
39 def get_list(self):
39 def get_list(self):
40 try:
40 try:
41 return self.__list
41 return self.__list
42 except AttributeError:
42 except AttributeError:
43 self.__list = self.split('\n')
43 self.__list = self.split('\n')
44 return self.__list
44 return self.__list
45
45
46 l = list = property(get_list)
46 l = list = property(get_list)
47
47
48 def get_spstr(self):
48 def get_spstr(self):
49 try:
49 try:
50 return self.__spstr
50 return self.__spstr
51 except AttributeError:
51 except AttributeError:
52 self.__spstr = self.replace('\n',' ')
52 self.__spstr = self.replace('\n',' ')
53 return self.__spstr
53 return self.__spstr
54
54
55 s = spstr = property(get_spstr)
55 s = spstr = property(get_spstr)
56
56
57 def get_nlstr(self):
57 def get_nlstr(self):
58 return self
58 return self
59
59
60 n = nlstr = property(get_nlstr)
60 n = nlstr = property(get_nlstr)
61
61
62 def get_paths(self):
62 def get_paths(self):
63 try:
63 try:
64 return self.__paths
64 return self.__paths
65 except AttributeError:
65 except AttributeError:
66 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
66 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
67 return self.__paths
67 return self.__paths
68
68
69 p = paths = property(get_paths)
69 p = paths = property(get_paths)
70
70
71 # FIXME: We need to reimplement type specific displayhook and then add this
71 # FIXME: We need to reimplement type specific displayhook and then add this
72 # back as a custom printer. This should also be moved outside utils into the
72 # back as a custom printer. This should also be moved outside utils into the
73 # core.
73 # core.
74
74
75 # def print_lsstring(arg):
75 # def print_lsstring(arg):
76 # """ Prettier (non-repr-like) and more informative printer for LSString """
76 # """ Prettier (non-repr-like) and more informative printer for LSString """
77 # print "LSString (.p, .n, .l, .s available). Value:"
77 # print "LSString (.p, .n, .l, .s available). Value:"
78 # print arg
78 # print arg
79 #
79 #
80 #
80 #
81 # print_lsstring = result_display.register(LSString)(print_lsstring)
81 # print_lsstring = result_display.register(LSString)(print_lsstring)
82
82
83
83
84 class SList(list):
84 class SList(list):
85 """List derivative with a special access attributes.
85 """List derivative with a special access attributes.
86
86
87 These are normal lists, but with the special attributes:
87 These are normal lists, but with the special attributes:
88
88
89 * .l (or .list) : value as list (the list itself).
89 * .l (or .list) : value as list (the list itself).
90 * .n (or .nlstr): value as a string, joined on newlines.
90 * .n (or .nlstr): value as a string, joined on newlines.
91 * .s (or .spstr): value as a string, joined on spaces.
91 * .s (or .spstr): value as a string, joined on spaces.
92 * .p (or .paths): list of path objects (requires path.py package)
92 * .p (or .paths): list of path objects (requires path.py package)
93
93
94 Any values which require transformations are computed only once and
94 Any values which require transformations are computed only once and
95 cached."""
95 cached."""
96
96
97 def get_list(self):
97 def get_list(self):
98 return self
98 return self
99
99
100 l = list = property(get_list)
100 l = list = property(get_list)
101
101
102 def get_spstr(self):
102 def get_spstr(self):
103 try:
103 try:
104 return self.__spstr
104 return self.__spstr
105 except AttributeError:
105 except AttributeError:
106 self.__spstr = ' '.join(self)
106 self.__spstr = ' '.join(self)
107 return self.__spstr
107 return self.__spstr
108
108
109 s = spstr = property(get_spstr)
109 s = spstr = property(get_spstr)
110
110
111 def get_nlstr(self):
111 def get_nlstr(self):
112 try:
112 try:
113 return self.__nlstr
113 return self.__nlstr
114 except AttributeError:
114 except AttributeError:
115 self.__nlstr = '\n'.join(self)
115 self.__nlstr = '\n'.join(self)
116 return self.__nlstr
116 return self.__nlstr
117
117
118 n = nlstr = property(get_nlstr)
118 n = nlstr = property(get_nlstr)
119
119
120 def get_paths(self):
120 def get_paths(self):
121 try:
121 try:
122 return self.__paths
122 return self.__paths
123 except AttributeError:
123 except AttributeError:
124 self.__paths = [Path(p) for p in self if os.path.exists(p)]
124 self.__paths = [Path(p) for p in self if os.path.exists(p)]
125 return self.__paths
125 return self.__paths
126
126
127 p = paths = property(get_paths)
127 p = paths = property(get_paths)
128
128
129 def grep(self, pattern, prune = False, field = None):
129 def grep(self, pattern, prune = False, field = None):
130 """ Return all strings matching 'pattern' (a regex or callable)
130 """ Return all strings matching 'pattern' (a regex or callable)
131
131
132 This is case-insensitive. If prune is true, return all items
132 This is case-insensitive. If prune is true, return all items
133 NOT matching the pattern.
133 NOT matching the pattern.
134
134
135 If field is specified, the match must occur in the specified
135 If field is specified, the match must occur in the specified
136 whitespace-separated field.
136 whitespace-separated field.
137
137
138 Examples::
138 Examples::
139
139
140 a.grep( lambda x: x.startswith('C') )
140 a.grep( lambda x: x.startswith('C') )
141 a.grep('Cha.*log', prune=1)
141 a.grep('Cha.*log', prune=1)
142 a.grep('chm', field=-1)
142 a.grep('chm', field=-1)
143 """
143 """
144
144
145 def match_target(s):
145 def match_target(s):
146 if field is None:
146 if field is None:
147 return s
147 return s
148 parts = s.split()
148 parts = s.split()
149 try:
149 try:
150 tgt = parts[field]
150 tgt = parts[field]
151 return tgt
151 return tgt
152 except IndexError:
152 except IndexError:
153 return ""
153 return ""
154
154
155 if isinstance(pattern, str):
155 if isinstance(pattern, str):
156 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
156 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
157 else:
157 else:
158 pred = pattern
158 pred = pattern
159 if not prune:
159 if not prune:
160 return SList([el for el in self if pred(match_target(el))])
160 return SList([el for el in self if pred(match_target(el))])
161 else:
161 else:
162 return SList([el for el in self if not pred(match_target(el))])
162 return SList([el for el in self if not pred(match_target(el))])
163
163
164 def fields(self, *fields):
164 def fields(self, *fields):
165 """ Collect whitespace-separated fields from string list
165 """ Collect whitespace-separated fields from string list
166
166
167 Allows quick awk-like usage of string lists.
167 Allows quick awk-like usage of string lists.
168
168
169 Example data (in var a, created by 'a = !ls -l')::
169 Example data (in var a, created by 'a = !ls -l')::
170
170
171 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
171 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
172 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
172 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
173
173
174 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
174 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
175 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
175 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
176 (note the joining by space).
176 (note the joining by space).
177 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
177 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
178
178
179 IndexErrors are ignored.
179 IndexErrors are ignored.
180
180
181 Without args, fields() just split()'s the strings.
181 Without args, fields() just split()'s the strings.
182 """
182 """
183 if len(fields) == 0:
183 if len(fields) == 0:
184 return [el.split() for el in self]
184 return [el.split() for el in self]
185
185
186 res = SList()
186 res = SList()
187 for el in [f.split() for f in self]:
187 for el in [f.split() for f in self]:
188 lineparts = []
188 lineparts = []
189
189
190 for fd in fields:
190 for fd in fields:
191 try:
191 try:
192 lineparts.append(el[fd])
192 lineparts.append(el[fd])
193 except IndexError:
193 except IndexError:
194 pass
194 pass
195 if lineparts:
195 if lineparts:
196 res.append(" ".join(lineparts))
196 res.append(" ".join(lineparts))
197
197
198 return res
198 return res
199
199
200 def sort(self,field= None, nums = False):
200 def sort(self,field= None, nums = False):
201 """ sort by specified fields (see fields())
201 """ sort by specified fields (see fields())
202
202
203 Example::
203 Example::
204
204
205 a.sort(1, nums = True)
205 a.sort(1, nums = True)
206
206
207 Sorts a by second field, in numerical order (so that 21 > 3)
207 Sorts a by second field, in numerical order (so that 21 > 3)
208
208
209 """
209 """
210
210
211 #decorate, sort, undecorate
211 #decorate, sort, undecorate
212 if field is not None:
212 if field is not None:
213 dsu = [[SList([line]).fields(field), line] for line in self]
213 dsu = [[SList([line]).fields(field), line] for line in self]
214 else:
214 else:
215 dsu = [[line, line] for line in self]
215 dsu = [[line, line] for line in self]
216 if nums:
216 if nums:
217 for i in range(len(dsu)):
217 for i in range(len(dsu)):
218 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
218 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
219 try:
219 try:
220 n = int(numstr)
220 n = int(numstr)
221 except ValueError:
221 except ValueError:
222 n = 0
222 n = 0
223 dsu[i][0] = n
223 dsu[i][0] = n
224
224
225
225
226 dsu.sort()
226 dsu.sort()
227 return SList([t[1] for t in dsu])
227 return SList([t[1] for t in dsu])
228
228
229
229
230 # FIXME: We need to reimplement type specific displayhook and then add this
230 # FIXME: We need to reimplement type specific displayhook and then add this
231 # back as a custom printer. This should also be moved outside utils into the
231 # back as a custom printer. This should also be moved outside utils into the
232 # core.
232 # core.
233
233
234 # def print_slist(arg):
234 # def print_slist(arg):
235 # """ Prettier (non-repr-like) and more informative printer for SList """
235 # """ Prettier (non-repr-like) and more informative printer for SList """
236 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
236 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
237 # if hasattr(arg, 'hideonce') and arg.hideonce:
237 # if hasattr(arg, 'hideonce') and arg.hideonce:
238 # arg.hideonce = False
238 # arg.hideonce = False
239 # return
239 # return
240 #
240 #
241 # nlprint(arg) # This was a nested list printer, now removed.
241 # nlprint(arg) # This was a nested list printer, now removed.
242 #
242 #
243 # print_slist = result_display.register(SList)(print_slist)
243 # print_slist = result_display.register(SList)(print_slist)
244
244
245
245
246 def indent(instr,nspaces=4, ntabs=0, flatten=False):
246 def indent(instr,nspaces=4, ntabs=0, flatten=False):
247 """Indent a string a given number of spaces or tabstops.
247 """Indent a string a given number of spaces or tabstops.
248
248
249 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
249 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
250
250
251 Parameters
251 Parameters
252 ----------
252 ----------
253 instr : basestring
253 instr : basestring
254 The string to be indented.
254 The string to be indented.
255 nspaces : int (default: 4)
255 nspaces : int (default: 4)
256 The number of spaces to be indented.
256 The number of spaces to be indented.
257 ntabs : int (default: 0)
257 ntabs : int (default: 0)
258 The number of tabs to be indented.
258 The number of tabs to be indented.
259 flatten : bool (default: False)
259 flatten : bool (default: False)
260 Whether to scrub existing indentation. If True, all lines will be
260 Whether to scrub existing indentation. If True, all lines will be
261 aligned to the same indentation. If False, existing indentation will
261 aligned to the same indentation. If False, existing indentation will
262 be strictly increased.
262 be strictly increased.
263
263
264 Returns
264 Returns
265 -------
265 -------
266 str|unicode : string indented by ntabs and nspaces.
266 str|unicode : string indented by ntabs and nspaces.
267
267
268 """
268 """
269 if instr is None:
269 if instr is None:
270 return
270 return
271 ind = '\t'*ntabs+' '*nspaces
271 ind = '\t'*ntabs+' '*nspaces
272 if flatten:
272 if flatten:
273 pat = re.compile(r'^\s*', re.MULTILINE)
273 pat = re.compile(r'^\s*', re.MULTILINE)
274 else:
274 else:
275 pat = re.compile(r'^', re.MULTILINE)
275 pat = re.compile(r'^', re.MULTILINE)
276 outstr = re.sub(pat, ind, instr)
276 outstr = re.sub(pat, ind, instr)
277 if outstr.endswith(os.linesep+ind):
277 if outstr.endswith(os.linesep+ind):
278 return outstr[:-len(ind)]
278 return outstr[:-len(ind)]
279 else:
279 else:
280 return outstr
280 return outstr
281
281
282
282
283 def list_strings(arg):
283 def list_strings(arg):
284 """Always return a list of strings, given a string or list of strings
284 """Always return a list of strings, given a string or list of strings
285 as input.
285 as input.
286
286
287 Examples
287 Examples
288 --------
288 --------
289 ::
289 ::
290
290
291 In [7]: list_strings('A single string')
291 In [7]: list_strings('A single string')
292 Out[7]: ['A single string']
292 Out[7]: ['A single string']
293
293
294 In [8]: list_strings(['A single string in a list'])
294 In [8]: list_strings(['A single string in a list'])
295 Out[8]: ['A single string in a list']
295 Out[8]: ['A single string in a list']
296
296
297 In [9]: list_strings(['A','list','of','strings'])
297 In [9]: list_strings(['A','list','of','strings'])
298 Out[9]: ['A', 'list', 'of', 'strings']
298 Out[9]: ['A', 'list', 'of', 'strings']
299 """
299 """
300
300
301 if isinstance(arg, str):
301 if isinstance(arg, str):
302 return [arg]
302 return [arg]
303 else:
303 else:
304 return arg
304 return arg
305
305
306
306
307 def marquee(txt='',width=78,mark='*'):
307 def marquee(txt='',width=78,mark='*'):
308 """Return the input string centered in a 'marquee'.
308 """Return the input string centered in a 'marquee'.
309
309
310 Examples
310 Examples
311 --------
311 --------
312 ::
312 ::
313
313
314 In [16]: marquee('A test',40)
314 In [16]: marquee('A test',40)
315 Out[16]: '**************** A test ****************'
315 Out[16]: '**************** A test ****************'
316
316
317 In [17]: marquee('A test',40,'-')
317 In [17]: marquee('A test',40,'-')
318 Out[17]: '---------------- A test ----------------'
318 Out[17]: '---------------- A test ----------------'
319
319
320 In [18]: marquee('A test',40,' ')
320 In [18]: marquee('A test',40,' ')
321 Out[18]: ' A test '
321 Out[18]: ' A test '
322
322
323 """
323 """
324 if not txt:
324 if not txt:
325 return (mark*width)[:width]
325 return (mark*width)[:width]
326 nmark = (width-len(txt)-2)//len(mark)//2
326 nmark = (width-len(txt)-2)//len(mark)//2
327 if nmark < 0: nmark =0
327 if nmark < 0: nmark =0
328 marks = mark*nmark
328 marks = mark*nmark
329 return '%s %s %s' % (marks,txt,marks)
329 return '%s %s %s' % (marks,txt,marks)
330
330
331
331
332 ini_spaces_re = re.compile(r'^(\s+)')
332 ini_spaces_re = re.compile(r'^(\s+)')
333
333
334 def num_ini_spaces(strng):
334 def num_ini_spaces(strng):
335 """Return the number of initial spaces in a string"""
335 """Return the number of initial spaces in a string"""
336 warnings.warn(
336 warnings.warn(
337 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
337 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
338 "It is considered fro removal in in future version. "
338 "It is considered fro removal in in future version. "
339 "Please open an issue if you believe it should be kept.",
339 "Please open an issue if you believe it should be kept.",
340 stacklevel=2,
340 stacklevel=2,
341 category=PendingDeprecationWarning,
341 category=PendingDeprecationWarning,
342 )
342 )
343 ini_spaces = ini_spaces_re.match(strng)
343 ini_spaces = ini_spaces_re.match(strng)
344 if ini_spaces:
344 if ini_spaces:
345 return ini_spaces.end()
345 return ini_spaces.end()
346 else:
346 else:
347 return 0
347 return 0
348
348
349
349
350 def format_screen(strng):
350 def format_screen(strng):
351 """Format a string for screen printing.
351 """Format a string for screen printing.
352
352
353 This removes some latex-type format codes."""
353 This removes some latex-type format codes."""
354 # Paragraph continue
354 # Paragraph continue
355 par_re = re.compile(r'\\$',re.MULTILINE)
355 par_re = re.compile(r'\\$',re.MULTILINE)
356 strng = par_re.sub('',strng)
356 strng = par_re.sub('',strng)
357 return strng
357 return strng
358
358
359
359
360 def dedent(text):
360 def dedent(text):
361 """Equivalent of textwrap.dedent that ignores unindented first line.
361 """Equivalent of textwrap.dedent that ignores unindented first line.
362
362
363 This means it will still dedent strings like:
363 This means it will still dedent strings like:
364 '''foo
364 '''foo
365 is a bar
365 is a bar
366 '''
366 '''
367
367
368 For use in wrap_paragraphs.
368 For use in wrap_paragraphs.
369 """
369 """
370
370
371 if text.startswith('\n'):
371 if text.startswith('\n'):
372 # text starts with blank line, don't ignore the first line
372 # text starts with blank line, don't ignore the first line
373 return textwrap.dedent(text)
373 return textwrap.dedent(text)
374
374
375 # split first line
375 # split first line
376 splits = text.split('\n',1)
376 splits = text.split('\n',1)
377 if len(splits) == 1:
377 if len(splits) == 1:
378 # only one line
378 # only one line
379 return textwrap.dedent(text)
379 return textwrap.dedent(text)
380
380
381 first, rest = splits
381 first, rest = splits
382 # dedent everything but the first line
382 # dedent everything but the first line
383 rest = textwrap.dedent(rest)
383 rest = textwrap.dedent(rest)
384 return '\n'.join([first, rest])
384 return '\n'.join([first, rest])
385
385
386
386
387 def wrap_paragraphs(text, ncols=80):
387 def wrap_paragraphs(text, ncols=80):
388 """Wrap multiple paragraphs to fit a specified width.
388 """Wrap multiple paragraphs to fit a specified width.
389
389
390 This is equivalent to textwrap.wrap, but with support for multiple
390 This is equivalent to textwrap.wrap, but with support for multiple
391 paragraphs, as separated by empty lines.
391 paragraphs, as separated by empty lines.
392
392
393 Returns
393 Returns
394 -------
394 -------
395 list of complete paragraphs, wrapped to fill `ncols` columns.
395 list of complete paragraphs, wrapped to fill `ncols` columns.
396 """
396 """
397 warnings.warn(
397 warnings.warn(
398 "`wrap_paragraphs` is Pending Deprecation since IPython 8.17."
398 "`wrap_paragraphs` is Pending Deprecation since IPython 8.17."
399 "It is considered fro removal in in future version. "
399 "It is considered fro removal in in future version. "
400 "Please open an issue if you believe it should be kept.",
400 "Please open an issue if you believe it should be kept.",
401 stacklevel=2,
401 stacklevel=2,
402 category=PendingDeprecationWarning,
402 category=PendingDeprecationWarning,
403 )
403 )
404 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
404 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
405 text = dedent(text).strip()
405 text = dedent(text).strip()
406 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
406 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
407 out_ps = []
407 out_ps = []
408 indent_re = re.compile(r'\n\s+', re.MULTILINE)
408 indent_re = re.compile(r'\n\s+', re.MULTILINE)
409 for p in paragraphs:
409 for p in paragraphs:
410 # presume indentation that survives dedent is meaningful formatting,
410 # presume indentation that survives dedent is meaningful formatting,
411 # so don't fill unless text is flush.
411 # so don't fill unless text is flush.
412 if indent_re.search(p) is None:
412 if indent_re.search(p) is None:
413 # wrap paragraph
413 # wrap paragraph
414 p = textwrap.fill(p, ncols)
414 p = textwrap.fill(p, ncols)
415 out_ps.append(p)
415 out_ps.append(p)
416 return out_ps
416 return out_ps
417
417
418
418
419 def strip_email_quotes(text):
419 def strip_email_quotes(text):
420 """Strip leading email quotation characters ('>').
420 """Strip leading email quotation characters ('>').
421
421
422 Removes any combination of leading '>' interspersed with whitespace that
422 Removes any combination of leading '>' interspersed with whitespace that
423 appears *identically* in all lines of the input text.
423 appears *identically* in all lines of the input text.
424
424
425 Parameters
425 Parameters
426 ----------
426 ----------
427 text : str
427 text : str
428
428
429 Examples
429 Examples
430 --------
430 --------
431
431
432 Simple uses::
432 Simple uses::
433
433
434 In [2]: strip_email_quotes('> > text')
434 In [2]: strip_email_quotes('> > text')
435 Out[2]: 'text'
435 Out[2]: 'text'
436
436
437 In [3]: strip_email_quotes('> > text\\n> > more')
437 In [3]: strip_email_quotes('> > text\\n> > more')
438 Out[3]: 'text\\nmore'
438 Out[3]: 'text\\nmore'
439
439
440 Note how only the common prefix that appears in all lines is stripped::
440 Note how only the common prefix that appears in all lines is stripped::
441
441
442 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
442 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
443 Out[4]: '> text\\n> more\\nmore...'
443 Out[4]: '> text\\n> more\\nmore...'
444
444
445 So if any line has no quote marks ('>'), then none are stripped from any
445 So if any line has no quote marks ('>'), then none are stripped from any
446 of them ::
446 of them ::
447
447
448 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
448 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
449 Out[5]: '> > text\\n> > more\\nlast different'
449 Out[5]: '> > text\\n> > more\\nlast different'
450 """
450 """
451 lines = text.splitlines()
451 lines = text.splitlines()
452 strip_len = 0
452 strip_len = 0
453
453
454 for characters in zip(*lines):
454 for characters in zip(*lines):
455 # Check if all characters in this position are the same
455 # Check if all characters in this position are the same
456 if len(set(characters)) > 1:
456 if len(set(characters)) > 1:
457 break
457 break
458 prefix_char = characters[0]
458 prefix_char = characters[0]
459
459
460 if prefix_char in string.whitespace or prefix_char == ">":
460 if prefix_char in string.whitespace or prefix_char == ">":
461 strip_len += 1
461 strip_len += 1
462 else:
462 else:
463 break
463 break
464
464
465 text = "\n".join([ln[strip_len:] for ln in lines])
465 text = "\n".join([ln[strip_len:] for ln in lines])
466 return text
466 return text
467
467
468
468
469 def strip_ansi(source):
469 def strip_ansi(source):
470 """
470 """
471 Remove ansi escape codes from text.
471 Remove ansi escape codes from text.
472
472
473 Parameters
473 Parameters
474 ----------
474 ----------
475 source : str
475 source : str
476 Source to remove the ansi from
476 Source to remove the ansi from
477 """
477 """
478 warnings.warn(
478 warnings.warn(
479 "`strip_ansi` is Pending Deprecation since IPython 8.17."
479 "`strip_ansi` is Pending Deprecation since IPython 8.17."
480 "It is considered fro removal in in future version. "
480 "It is considered fro removal in in future version. "
481 "Please open an issue if you believe it should be kept.",
481 "Please open an issue if you believe it should be kept.",
482 stacklevel=2,
482 stacklevel=2,
483 category=PendingDeprecationWarning,
483 category=PendingDeprecationWarning,
484 )
484 )
485
485
486 return re.sub(r'\033\[(\d|;)+?m', '', source)
486 return re.sub(r'\033\[(\d|;)+?m', '', source)
487
487
488
488
489 class EvalFormatter(Formatter):
489 class EvalFormatter(Formatter):
490 """A String Formatter that allows evaluation of simple expressions.
490 """A String Formatter that allows evaluation of simple expressions.
491
491
492 Note that this version interprets a `:` as specifying a format string (as per
492 Note that this version interprets a `:` as specifying a format string (as per
493 standard string formatting), so if slicing is required, you must explicitly
493 standard string formatting), so if slicing is required, you must explicitly
494 create a slice.
494 create a slice.
495
495
496 This is to be used in templating cases, such as the parallel batch
496 This is to be used in templating cases, such as the parallel batch
497 script templates, where simple arithmetic on arguments is useful.
497 script templates, where simple arithmetic on arguments is useful.
498
498
499 Examples
499 Examples
500 --------
500 --------
501 ::
501 ::
502
502
503 In [1]: f = EvalFormatter()
503 In [1]: f = EvalFormatter()
504 In [2]: f.format('{n//4}', n=8)
504 In [2]: f.format('{n//4}', n=8)
505 Out[2]: '2'
505 Out[2]: '2'
506
506
507 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
507 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
508 Out[3]: 'll'
508 Out[3]: 'll'
509 """
509 """
510 def get_field(self, name, args, kwargs):
510 def get_field(self, name, args, kwargs):
511 v = eval(name, kwargs)
511 v = eval(name, kwargs)
512 return v, name
512 return v, name
513
513
514 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
514 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
515 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
515 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
516 # above, it should be possible to remove FullEvalFormatter.
516 # above, it should be possible to remove FullEvalFormatter.
517
517
518 class FullEvalFormatter(Formatter):
518 class FullEvalFormatter(Formatter):
519 """A String Formatter that allows evaluation of simple expressions.
519 """A String Formatter that allows evaluation of simple expressions.
520
520
521 Any time a format key is not found in the kwargs,
521 Any time a format key is not found in the kwargs,
522 it will be tried as an expression in the kwargs namespace.
522 it will be tried as an expression in the kwargs namespace.
523
523
524 Note that this version allows slicing using [1:2], so you cannot specify
524 Note that this version allows slicing using [1:2], so you cannot specify
525 a format string. Use :class:`EvalFormatter` to permit format strings.
525 a format string. Use :class:`EvalFormatter` to permit format strings.
526
526
527 Examples
527 Examples
528 --------
528 --------
529 ::
529 ::
530
530
531 In [1]: f = FullEvalFormatter()
531 In [1]: f = FullEvalFormatter()
532 In [2]: f.format('{n//4}', n=8)
532 In [2]: f.format('{n//4}', n=8)
533 Out[2]: '2'
533 Out[2]: '2'
534
534
535 In [3]: f.format('{list(range(5))[2:4]}')
535 In [3]: f.format('{list(range(5))[2:4]}')
536 Out[3]: '[2, 3]'
536 Out[3]: '[2, 3]'
537
537
538 In [4]: f.format('{3*2}')
538 In [4]: f.format('{3*2}')
539 Out[4]: '6'
539 Out[4]: '6'
540 """
540 """
541 # copied from Formatter._vformat with minor changes to allow eval
541 # copied from Formatter._vformat with minor changes to allow eval
542 # and replace the format_spec code with slicing
542 # and replace the format_spec code with slicing
543 def vformat(self, format_string:str, args, kwargs)->str:
543 def vformat(self, format_string:str, args, kwargs)->str:
544 result = []
544 result = []
545 for literal_text, field_name, format_spec, conversion in \
545 for literal_text, field_name, format_spec, conversion in \
546 self.parse(format_string):
546 self.parse(format_string):
547
547
548 # output the literal text
548 # output the literal text
549 if literal_text:
549 if literal_text:
550 result.append(literal_text)
550 result.append(literal_text)
551
551
552 # if there's a field, output it
552 # if there's a field, output it
553 if field_name is not None:
553 if field_name is not None:
554 # this is some markup, find the object and do
554 # this is some markup, find the object and do
555 # the formatting
555 # the formatting
556
556
557 if format_spec:
557 if format_spec:
558 # override format spec, to allow slicing:
558 # override format spec, to allow slicing:
559 field_name = ':'.join([field_name, format_spec])
559 field_name = ':'.join([field_name, format_spec])
560
560
561 # eval the contents of the field for the object
561 # eval the contents of the field for the object
562 # to be formatted
562 # to be formatted
563 obj = eval(field_name, kwargs)
563 obj = eval(field_name, kwargs)
564
564
565 # do any conversion on the resulting object
565 # do any conversion on the resulting object
566 obj = self.convert_field(obj, conversion)
566 obj = self.convert_field(obj, conversion)
567
567
568 # format the object and append to the result
568 # format the object and append to the result
569 result.append(self.format_field(obj, ''))
569 result.append(self.format_field(obj, ''))
570
570
571 return ''.join(result)
571 return ''.join(result)
572
572
573
573
574 class DollarFormatter(FullEvalFormatter):
574 class DollarFormatter(FullEvalFormatter):
575 """Formatter allowing Itpl style $foo replacement, for names and attribute
575 """Formatter allowing Itpl style $foo replacement, for names and attribute
576 access only. Standard {foo} replacement also works, and allows full
576 access only. Standard {foo} replacement also works, and allows full
577 evaluation of its arguments.
577 evaluation of its arguments.
578
578
579 Examples
579 Examples
580 --------
580 --------
581 ::
581 ::
582
582
583 In [1]: f = DollarFormatter()
583 In [1]: f = DollarFormatter()
584 In [2]: f.format('{n//4}', n=8)
584 In [2]: f.format('{n//4}', n=8)
585 Out[2]: '2'
585 Out[2]: '2'
586
586
587 In [3]: f.format('23 * 76 is $result', result=23*76)
587 In [3]: f.format('23 * 76 is $result', result=23*76)
588 Out[3]: '23 * 76 is 1748'
588 Out[3]: '23 * 76 is 1748'
589
589
590 In [4]: f.format('$a or {b}', a=1, b=2)
590 In [4]: f.format('$a or {b}', a=1, b=2)
591 Out[4]: '1 or 2'
591 Out[4]: '1 or 2'
592 """
592 """
593 _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
593 _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
594 def parse(self, fmt_string):
594 def parse(self, fmt_string):
595 for literal_txt, field_name, format_spec, conversion \
595 for literal_txt, field_name, format_spec, conversion \
596 in Formatter.parse(self, fmt_string):
596 in Formatter.parse(self, fmt_string):
597
597
598 # Find $foo patterns in the literal text.
598 # Find $foo patterns in the literal text.
599 continue_from = 0
599 continue_from = 0
600 txt = ""
600 txt = ""
601 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
601 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
602 new_txt, new_field = m.group(1,2)
602 new_txt, new_field = m.group(1,2)
603 # $$foo --> $foo
603 # $$foo --> $foo
604 if new_field.startswith("$"):
604 if new_field.startswith("$"):
605 txt += new_txt + new_field
605 txt += new_txt + new_field
606 else:
606 else:
607 yield (txt + new_txt, new_field, "", None)
607 yield (txt + new_txt, new_field, "", None)
608 txt = ""
608 txt = ""
609 continue_from = m.end()
609 continue_from = m.end()
610
610
611 # Re-yield the {foo} style pattern
611 # Re-yield the {foo} style pattern
612 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
612 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
613
613
614 def __repr__(self):
614 def __repr__(self):
615 return "<DollarFormatter>"
615 return "<DollarFormatter>"
616
616
617 #-----------------------------------------------------------------------------
617 #-----------------------------------------------------------------------------
618 # Utils to columnize a list of string
618 # Utils to columnize a list of string
619 #-----------------------------------------------------------------------------
619 #-----------------------------------------------------------------------------
620
620
621 def _col_chunks(l, max_rows, row_first=False):
621 def _col_chunks(l, max_rows, row_first=False):
622 """Yield successive max_rows-sized column chunks from l."""
622 """Yield successive max_rows-sized column chunks from l."""
623 if row_first:
623 if row_first:
624 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
624 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
625 for i in range(ncols):
625 for i in range(ncols):
626 yield [l[j] for j in range(i, len(l), ncols)]
626 yield [l[j] for j in range(i, len(l), ncols)]
627 else:
627 else:
628 for i in range(0, len(l), max_rows):
628 for i in range(0, len(l), max_rows):
629 yield l[i:(i + max_rows)]
629 yield l[i:(i + max_rows)]
630
630
631
631
632 def _find_optimal(rlist, row_first:bool, separator_size:int, displaywidth:int):
632 def _find_optimal(rlist, row_first: bool, separator_size: int, displaywidth: int):
633 """Calculate optimal info to columnize a list of string"""
633 """Calculate optimal info to columnize a list of string"""
634 for max_rows in range(1, len(rlist) + 1):
634 for max_rows in range(1, len(rlist) + 1):
635 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
635 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
636 sumlength = sum(col_widths)
636 sumlength = sum(col_widths)
637 ncols = len(col_widths)
637 ncols = len(col_widths)
638 if sumlength + separator_size * (ncols - 1) <= displaywidth:
638 if sumlength + separator_size * (ncols - 1) <= displaywidth:
639 break
639 break
640 return {'num_columns': ncols,
640 return {'num_columns': ncols,
641 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
641 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
642 'max_rows': max_rows,
642 'max_rows': max_rows,
643 'column_widths': col_widths
643 'column_widths': col_widths
644 }
644 }
645
645
646
646
647 def _get_or_default(mylist, i, default=None):
647 def _get_or_default(mylist, i, default=None):
648 """return list item number, or default if don't exist"""
648 """return list item number, or default if don't exist"""
649 if i >= len(mylist):
649 if i >= len(mylist):
650 return default
650 return default
651 else :
651 else :
652 return mylist[i]
652 return mylist[i]
653
653
654
654
655 def compute_item_matrix(items, row_first:bool=False, empty=None,*, separator_size=2, displaywidth=80) -> Tuple[List[List[int]], Dict[str, int]] :
655 def compute_item_matrix(
656 items, row_first: bool = False, empty=None, *, separator_size=2, displaywidth=80
657 ) -> Tuple[List[List[int]], Dict[str, int]]:
656 """Returns a nested list, and info to columnize items
658 """Returns a nested list, and info to columnize items
657
659
658 Parameters
660 Parameters
659 ----------
661 ----------
660 items
662 items
661 list of strings to columize
663 list of strings to columize
662 row_first : (default False)
664 row_first : (default False)
663 Whether to compute columns for a row-first matrix instead of
665 Whether to compute columns for a row-first matrix instead of
664 column-first (default).
666 column-first (default).
665 empty : (default None)
667 empty : (default None)
666 default value to fill list if needed
668 default value to fill list if needed
667 separator_size : int (default=2)
669 separator_size : int (default=2)
668 How much characters will be used as a separation between each columns.
670 How much characters will be used as a separation between each columns.
669 displaywidth : int (default=80)
671 displaywidth : int (default=80)
670 The width of the area onto which the columns should enter
672 The width of the area onto which the columns should enter
671
673
672 Returns
674 Returns
673 -------
675 -------
674 strings_matrix
676 strings_matrix
675 nested list of string, the outer most list contains as many list as
677 nested list of string, the outer most list contains as many list as
676 rows, the innermost lists have each as many element as columns. If the
678 rows, the innermost lists have each as many element as columns. If the
677 total number of elements in `items` does not equal the product of
679 total number of elements in `items` does not equal the product of
678 rows*columns, the last element of some lists are filled with `None`.
680 rows*columns, the last element of some lists are filled with `None`.
679 dict_info
681 dict_info
680 some info to make columnize easier:
682 some info to make columnize easier:
681
683
682 num_columns
684 num_columns
683 number of columns
685 number of columns
684 max_rows
686 max_rows
685 maximum number of rows (final number may be less)
687 maximum number of rows (final number may be less)
686 column_widths
688 column_widths
687 list of with of each columns
689 list of with of each columns
688 optimal_separator_width
690 optimal_separator_width
689 best separator width between columns
691 best separator width between columns
690
692
691 Examples
693 Examples
692 --------
694 --------
693 ::
695 ::
694
696
695 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
697 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
696 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
698 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
697 In [3]: list
699 In [3]: list
698 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
700 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
699 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
701 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
700 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
702 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
701 Out[5]: True
703 Out[5]: True
702 """
704 """
703 warnings.warn(
705 warnings.warn(
704 "`compute_item_matrix` is Pending Deprecation since IPython 8.17."
706 "`compute_item_matrix` is Pending Deprecation since IPython 8.17."
705 "It is considered fro removal in in future version. "
707 "It is considered fro removal in in future version. "
706 "Please open an issue if you believe it should be kept.",
708 "Please open an issue if you believe it should be kept.",
707 stacklevel=2,
709 stacklevel=2,
708 category=PendingDeprecationWarning,
710 category=PendingDeprecationWarning,
709 )
711 )
710 info = _find_optimal(list(map(len, items)), row_first, separator_size=separator_size, displaywidth=displaywidth)
712 info = _find_optimal(
711 nrow, ncol = info['max_rows'], info['num_columns']
713 list(map(len, items)),
714 row_first,
715 separator_size=separator_size,
716 displaywidth=displaywidth,
717 )
718 nrow, ncol = info["max_rows"], info["num_columns"]
712 if row_first:
719 if row_first:
713 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
720 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
714 else:
721 else:
715 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
722 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
716
723
717
724
718 def columnize(items, row_first=False, separator=" ", displaywidth=80, spread=False):
725 def columnize(items, row_first=False, separator=" ", displaywidth=80, spread=False):
719 """Transform a list of strings into a single string with columns.
726 """Transform a list of strings into a single string with columns.
720
727
721 Parameters
728 Parameters
722 ----------
729 ----------
723 items : sequence of strings
730 items : sequence of strings
724 The strings to process.
731 The strings to process.
725 row_first : (default False)
732 row_first : (default False)
726 Whether to compute columns for a row-first matrix instead of
733 Whether to compute columns for a row-first matrix instead of
727 column-first (default).
734 column-first (default).
728 separator : str, optional [default is two spaces]
735 separator : str, optional [default is two spaces]
729 The string that separates columns.
736 The string that separates columns.
730 displaywidth : int, optional [default is 80]
737 displaywidth : int, optional [default is 80]
731 Width of the display in number of characters.
738 Width of the display in number of characters.
732
739
733 Returns
740 Returns
734 -------
741 -------
735 The formatted string.
742 The formatted string.
736 """
743 """
737 warnings.warn(
744 warnings.warn(
738 "`columnize` is Pending Deprecation since IPython 8.17."
745 "`columnize` is Pending Deprecation since IPython 8.17."
739 "It is considered fro removal in in future version. "
746 "It is considered fro removal in in future version. "
740 "Please open an issue if you believe it should be kept.",
747 "Please open an issue if you believe it should be kept.",
741 stacklevel=2,
748 stacklevel=2,
742 category=PendingDeprecationWarning,
749 category=PendingDeprecationWarning,
743 )
750 )
744 if not items:
751 if not items:
745 return '\n'
752 return "\n"
746 matrix:List[List[int]]
753 matrix: List[List[int]]
747 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
754 matrix, info = compute_item_matrix(
755 items,
756 row_first=row_first,
757 separator_size=len(separator),
758 displaywidth=displaywidth,
759 )
748 if spread:
760 if spread:
749 separator = separator.ljust(int(info['optimal_separator_width']))
761 separator = separator.ljust(int(info["optimal_separator_width"]))
750 fmatrix:List[filter[int]] = [filter(None, x) for x in matrix]
762 fmatrix: List[filter[int]] = [filter(None, x) for x in matrix]
751 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
763 sjoin = lambda x: separator.join(
752 return '\n'.join(map(sjoin, fmatrix))+'\n'
764 [y.ljust(w, " ") for y, w in zip(x, info["column_widths"])]
765 )
766 return "\n".join(map(sjoin, fmatrix)) + "\n"
753
767
754
768
755 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
769 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
756 """
770 """
757 Return a string with a natural enumeration of items
771 Return a string with a natural enumeration of items
758
772
759 >>> get_text_list(['a', 'b', 'c', 'd'])
773 >>> get_text_list(['a', 'b', 'c', 'd'])
760 'a, b, c and d'
774 'a, b, c and d'
761 >>> get_text_list(['a', 'b', 'c'], ' or ')
775 >>> get_text_list(['a', 'b', 'c'], ' or ')
762 'a, b or c'
776 'a, b or c'
763 >>> get_text_list(['a', 'b', 'c'], ', ')
777 >>> get_text_list(['a', 'b', 'c'], ', ')
764 'a, b, c'
778 'a, b, c'
765 >>> get_text_list(['a', 'b'], ' or ')
779 >>> get_text_list(['a', 'b'], ' or ')
766 'a or b'
780 'a or b'
767 >>> get_text_list(['a'])
781 >>> get_text_list(['a'])
768 'a'
782 'a'
769 >>> get_text_list([])
783 >>> get_text_list([])
770 ''
784 ''
771 >>> get_text_list(['a', 'b'], wrap_item_with="`")
785 >>> get_text_list(['a', 'b'], wrap_item_with="`")
772 '`a` and `b`'
786 '`a` and `b`'
773 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
787 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
774 'a + b + c = d'
788 'a + b + c = d'
775 """
789 """
776 if len(list_) == 0:
790 if len(list_) == 0:
777 return ''
791 return ''
778 if wrap_item_with:
792 if wrap_item_with:
779 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
793 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
780 item in list_]
794 item in list_]
781 if len(list_) == 1:
795 if len(list_) == 1:
782 return list_[0]
796 return list_[0]
783 return '%s%s%s' % (
797 return '%s%s%s' % (
784 sep.join(i for i in list_[:-1]),
798 sep.join(i for i in list_[:-1]),
785 last_sep, list_[-1])
799 last_sep, list_[-1])
@@ -1,123 +1,123 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for timing code execution.
3 Utilities for timing code execution.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import time
17 import time
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Code
20 # Code
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 # If possible (Unix), use the resource module instead of time.clock()
23 # If possible (Unix), use the resource module instead of time.clock()
24 try:
24 try:
25 import resource
25 import resource
26 except ModuleNotFoundError:
26 except ModuleNotFoundError:
27 resource = None #type: ignore [assignment]
27 resource = None # type: ignore [assignment]
28
28
29 # Some implementations (like jyputerlite) don't have getrusage
29 # Some implementations (like jyputerlite) don't have getrusage
30 if resource is not None and hasattr(resource, "getrusage"):
30 if resource is not None and hasattr(resource, "getrusage"):
31 def clocku():
31 def clocku():
32 """clocku() -> floating point number
32 """clocku() -> floating point number
33
33
34 Return the *USER* CPU time in seconds since the start of the process.
34 Return the *USER* CPU time in seconds since the start of the process.
35 This is done via a call to resource.getrusage, so it avoids the
35 This is done via a call to resource.getrusage, so it avoids the
36 wraparound problems in time.clock()."""
36 wraparound problems in time.clock()."""
37
37
38 return resource.getrusage(resource.RUSAGE_SELF)[0]
38 return resource.getrusage(resource.RUSAGE_SELF)[0]
39
39
40 def clocks():
40 def clocks():
41 """clocks() -> floating point number
41 """clocks() -> floating point number
42
42
43 Return the *SYSTEM* CPU time in seconds since the start of the process.
43 Return the *SYSTEM* CPU time in seconds since the start of the process.
44 This is done via a call to resource.getrusage, so it avoids the
44 This is done via a call to resource.getrusage, so it avoids the
45 wraparound problems in time.clock()."""
45 wraparound problems in time.clock()."""
46
46
47 return resource.getrusage(resource.RUSAGE_SELF)[1]
47 return resource.getrusage(resource.RUSAGE_SELF)[1]
48
48
49 def clock():
49 def clock():
50 """clock() -> floating point number
50 """clock() -> floating point number
51
51
52 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of
52 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of
53 the process. This is done via a call to resource.getrusage, so it
53 the process. This is done via a call to resource.getrusage, so it
54 avoids the wraparound problems in time.clock()."""
54 avoids the wraparound problems in time.clock()."""
55
55
56 u,s = resource.getrusage(resource.RUSAGE_SELF)[:2]
56 u,s = resource.getrusage(resource.RUSAGE_SELF)[:2]
57 return u+s
57 return u+s
58
58
59 def clock2():
59 def clock2():
60 """clock2() -> (t_user,t_system)
60 """clock2() -> (t_user,t_system)
61
61
62 Similar to clock(), but return a tuple of user/system times."""
62 Similar to clock(), but return a tuple of user/system times."""
63 return resource.getrusage(resource.RUSAGE_SELF)[:2]
63 return resource.getrusage(resource.RUSAGE_SELF)[:2]
64
64
65 else:
65 else:
66 # There is no distinction of user/system time under windows, so we just use
66 # There is no distinction of user/system time under windows, so we just use
67 # time.process_time() for everything...
67 # time.process_time() for everything...
68 clocku = clocks = clock = time.process_time
68 clocku = clocks = clock = time.process_time
69
69
70 def clock2():
70 def clock2():
71 """Under windows, system CPU time can't be measured.
71 """Under windows, system CPU time can't be measured.
72
72
73 This just returns process_time() and zero."""
73 This just returns process_time() and zero."""
74 return time.process_time(), 0.0
74 return time.process_time(), 0.0
75
75
76
76
77 def timings_out(reps,func,*args,**kw):
77 def timings_out(reps,func,*args,**kw):
78 """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output)
78 """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output)
79
79
80 Execute a function reps times, return a tuple with the elapsed total
80 Execute a function reps times, return a tuple with the elapsed total
81 CPU time in seconds, the time per call and the function's output.
81 CPU time in seconds, the time per call and the function's output.
82
82
83 Under Unix, the return value is the sum of user+system time consumed by
83 Under Unix, the return value is the sum of user+system time consumed by
84 the process, computed via the resource module. This prevents problems
84 the process, computed via the resource module. This prevents problems
85 related to the wraparound effect which the time.clock() function has.
85 related to the wraparound effect which the time.clock() function has.
86
86
87 Under Windows the return value is in wall clock seconds. See the
87 Under Windows the return value is in wall clock seconds. See the
88 documentation for the time module for more details."""
88 documentation for the time module for more details."""
89
89
90 reps = int(reps)
90 reps = int(reps)
91 assert reps >=1, 'reps must be >= 1'
91 assert reps >=1, 'reps must be >= 1'
92 if reps==1:
92 if reps==1:
93 start = clock()
93 start = clock()
94 out = func(*args,**kw)
94 out = func(*args,**kw)
95 tot_time = clock()-start
95 tot_time = clock()-start
96 else:
96 else:
97 rng = range(reps-1) # the last time is executed separately to store output
97 rng = range(reps-1) # the last time is executed separately to store output
98 start = clock()
98 start = clock()
99 for dummy in rng: func(*args,**kw)
99 for dummy in rng: func(*args,**kw)
100 out = func(*args,**kw) # one last time
100 out = func(*args,**kw) # one last time
101 tot_time = clock()-start
101 tot_time = clock()-start
102 av_time = tot_time / reps
102 av_time = tot_time / reps
103 return tot_time,av_time,out
103 return tot_time,av_time,out
104
104
105
105
106 def timings(reps,func,*args,**kw):
106 def timings(reps,func,*args,**kw):
107 """timings(reps,func,*args,**kw) -> (t_total,t_per_call)
107 """timings(reps,func,*args,**kw) -> (t_total,t_per_call)
108
108
109 Execute a function reps times, return a tuple with the elapsed total CPU
109 Execute a function reps times, return a tuple with the elapsed total CPU
110 time in seconds and the time per call. These are just the first two values
110 time in seconds and the time per call. These are just the first two values
111 in timings_out()."""
111 in timings_out()."""
112
112
113 return timings_out(reps,func,*args,**kw)[0:2]
113 return timings_out(reps,func,*args,**kw)[0:2]
114
114
115
115
116 def timing(func,*args,**kw):
116 def timing(func,*args,**kw):
117 """timing(func,*args,**kw) -> t_total
117 """timing(func,*args,**kw) -> t_total
118
118
119 Execute a function once, return the elapsed total CPU time in
119 Execute a function once, return the elapsed total CPU time in
120 seconds. This is just the first value in timings_out()."""
120 seconds. This is just the first value in timings_out()."""
121
121
122 return timings_out(1,func,*args,**kw)[0]
122 return timings_out(1,func,*args,**kw)[0]
123
123
@@ -1,157 +1,160 b''
1 """Token-related utilities"""
1 """Token-related utilities"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from collections import namedtuple
6 from collections import namedtuple
7 from io import StringIO
7 from io import StringIO
8 from keyword import iskeyword
8 from keyword import iskeyword
9
9
10 import tokenize
10 import tokenize
11 from tokenize import TokenInfo
11 from tokenize import TokenInfo
12 from typing import List, Optional
12 from typing import List, Optional
13
13
14
14
15 Token = namedtuple('Token', ['token', 'text', 'start', 'end', 'line'])
15 Token = namedtuple('Token', ['token', 'text', 'start', 'end', 'line'])
16
16
17 def generate_tokens(readline):
17 def generate_tokens(readline):
18 """wrap generate_tkens to catch EOF errors"""
18 """wrap generate_tkens to catch EOF errors"""
19 try:
19 try:
20 for token in tokenize.generate_tokens(readline):
20 for token in tokenize.generate_tokens(readline):
21 yield token
21 yield token
22 except tokenize.TokenError:
22 except tokenize.TokenError:
23 # catch EOF error
23 # catch EOF error
24 return
24 return
25
25
26
26
27 def generate_tokens_catch_errors(readline, extra_errors_to_catch:Optional[List[str]]=None):
27 def generate_tokens_catch_errors(
28 readline, extra_errors_to_catch: Optional[List[str]] = None
29 ):
28 default_errors_to_catch = [
30 default_errors_to_catch = [
29 "unterminated string literal",
31 "unterminated string literal",
30 "invalid non-printable character",
32 "invalid non-printable character",
31 "after line continuation character",
33 "after line continuation character",
32 ]
34 ]
33 assert extra_errors_to_catch is None or isinstance(extra_errors_to_catch, list)
35 assert extra_errors_to_catch is None or isinstance(extra_errors_to_catch, list)
34 errors_to_catch = default_errors_to_catch + (extra_errors_to_catch or [])
36 errors_to_catch = default_errors_to_catch + (extra_errors_to_catch or [])
35
37
36 tokens:List[TokenInfo] = []
38 tokens: List[TokenInfo] = []
37 try:
39 try:
38 for token in tokenize.generate_tokens(readline):
40 for token in tokenize.generate_tokens(readline):
39 tokens.append(token)
41 tokens.append(token)
40 yield token
42 yield token
41 except tokenize.TokenError as exc:
43 except tokenize.TokenError as exc:
42 if any(error in exc.args[0] for error in errors_to_catch):
44 if any(error in exc.args[0] for error in errors_to_catch):
43 if tokens:
45 if tokens:
44 start = tokens[-1].start[0], tokens[-1].end[0]
46 start = tokens[-1].start[0], tokens[-1].end[0]
45 end = start
47 end = start
46 line = tokens[-1].line
48 line = tokens[-1].line
47 else:
49 else:
48 start = end = (1, 0)
50 start = end = (1, 0)
49 line = ""
51 line = ""
50 yield tokenize.TokenInfo(tokenize.ERRORTOKEN, "", start, end, line)
52 yield tokenize.TokenInfo(tokenize.ERRORTOKEN, "", start, end, line)
51 else:
53 else:
52 # Catch EOF
54 # Catch EOF
53 raise
55 raise
54
56
55
57
56 def line_at_cursor(cell, cursor_pos=0):
58 def line_at_cursor(cell, cursor_pos=0):
57 """Return the line in a cell at a given cursor position
59 """Return the line in a cell at a given cursor position
58
60
59 Used for calling line-based APIs that don't support multi-line input, yet.
61 Used for calling line-based APIs that don't support multi-line input, yet.
60
62
61 Parameters
63 Parameters
62 ----------
64 ----------
63 cell : str
65 cell : str
64 multiline block of text
66 multiline block of text
65 cursor_pos : integer
67 cursor_pos : integer
66 the cursor position
68 the cursor position
67
69
68 Returns
70 Returns
69 -------
71 -------
70 (line, offset): (string, integer)
72 (line, offset): (string, integer)
71 The line with the current cursor, and the character offset of the start of the line.
73 The line with the current cursor, and the character offset of the start of the line.
72 """
74 """
73 offset = 0
75 offset = 0
74 lines = cell.splitlines(True)
76 lines = cell.splitlines(True)
75 for line in lines:
77 for line in lines:
76 next_offset = offset + len(line)
78 next_offset = offset + len(line)
77 if not line.endswith('\n'):
79 if not line.endswith('\n'):
78 # If the last line doesn't have a trailing newline, treat it as if
80 # If the last line doesn't have a trailing newline, treat it as if
79 # it does so that the cursor at the end of the line still counts
81 # it does so that the cursor at the end of the line still counts
80 # as being on that line.
82 # as being on that line.
81 next_offset += 1
83 next_offset += 1
82 if next_offset > cursor_pos:
84 if next_offset > cursor_pos:
83 break
85 break
84 offset = next_offset
86 offset = next_offset
85 else:
87 else:
86 line = ""
88 line = ""
87 return (line, offset)
89 return (line, offset)
88
90
89 def token_at_cursor(cell:str, cursor_pos:int=0):
91
92 def token_at_cursor(cell: str, cursor_pos: int = 0):
90 """Get the token at a given cursor
93 """Get the token at a given cursor
91
94
92 Used for introspection.
95 Used for introspection.
93
96
94 Function calls are prioritized, so the token for the callable will be returned
97 Function calls are prioritized, so the token for the callable will be returned
95 if the cursor is anywhere inside the call.
98 if the cursor is anywhere inside the call.
96
99
97 Parameters
100 Parameters
98 ----------
101 ----------
99 cell : str
102 cell : str
100 A block of Python code
103 A block of Python code
101 cursor_pos : int
104 cursor_pos : int
102 The location of the cursor in the block where the token should be found
105 The location of the cursor in the block where the token should be found
103 """
106 """
104 names:List[str] = []
107 names: List[str] = []
105 tokens:List[Token] = []
108 tokens: List[Token] = []
106 call_names = []
109 call_names = []
107
110
108 offsets = {1: 0} # lines start at 1
111 offsets = {1: 0} # lines start at 1
109 for tup in generate_tokens(StringIO(cell).readline):
112 for tup in generate_tokens(StringIO(cell).readline):
110
113
111 tok = Token(*tup)
114 tok = Token(*tup)
112
115
113 # token, text, start, end, line = tup
116 # token, text, start, end, line = tup
114 start_line, start_col = tok.start
117 start_line, start_col = tok.start
115 end_line, end_col = tok.end
118 end_line, end_col = tok.end
116 if end_line + 1 not in offsets:
119 if end_line + 1 not in offsets:
117 # keep track of offsets for each line
120 # keep track of offsets for each line
118 lines = tok.line.splitlines(True)
121 lines = tok.line.splitlines(True)
119 for lineno, line in enumerate(lines, start_line + 1):
122 for lineno, line in enumerate(lines, start_line + 1):
120 if lineno not in offsets:
123 if lineno not in offsets:
121 offsets[lineno] = offsets[lineno-1] + len(line)
124 offsets[lineno] = offsets[lineno-1] + len(line)
122
125
123 offset = offsets[start_line]
126 offset = offsets[start_line]
124 # allow '|foo' to find 'foo' at the beginning of a line
127 # allow '|foo' to find 'foo' at the beginning of a line
125 boundary = cursor_pos + 1 if start_col == 0 else cursor_pos
128 boundary = cursor_pos + 1 if start_col == 0 else cursor_pos
126 if offset + start_col >= boundary:
129 if offset + start_col >= boundary:
127 # current token starts after the cursor,
130 # current token starts after the cursor,
128 # don't consume it
131 # don't consume it
129 break
132 break
130
133
131 if tok.token == tokenize.NAME and not iskeyword(tok.text):
134 if tok.token == tokenize.NAME and not iskeyword(tok.text):
132 if names and tokens and tokens[-1].token == tokenize.OP and tokens[-1].text == '.':
135 if names and tokens and tokens[-1].token == tokenize.OP and tokens[-1].text == '.':
133 names[-1] = "%s.%s" % (names[-1], tok.text)
136 names[-1] = "%s.%s" % (names[-1], tok.text)
134 else:
137 else:
135 names.append(tok.text)
138 names.append(tok.text)
136 elif tok.token == tokenize.OP:
139 elif tok.token == tokenize.OP:
137 if tok.text == '=' and names:
140 if tok.text == '=' and names:
138 # don't inspect the lhs of an assignment
141 # don't inspect the lhs of an assignment
139 names.pop(-1)
142 names.pop(-1)
140 if tok.text == '(' and names:
143 if tok.text == '(' and names:
141 # if we are inside a function call, inspect the function
144 # if we are inside a function call, inspect the function
142 call_names.append(names[-1])
145 call_names.append(names[-1])
143 elif tok.text == ')' and call_names:
146 elif tok.text == ')' and call_names:
144 call_names.pop(-1)
147 call_names.pop(-1)
145
148
146 tokens.append(tok)
149 tokens.append(tok)
147
150
148 if offsets[end_line] + end_col > cursor_pos:
151 if offsets[end_line] + end_col > cursor_pos:
149 # we found the cursor, stop reading
152 # we found the cursor, stop reading
150 break
153 break
151
154
152 if call_names:
155 if call_names:
153 return call_names[-1]
156 return call_names[-1]
154 elif names:
157 elif names:
155 return names[-1]
158 return names[-1]
156 else:
159 else:
157 return ''
160 return ''
General Comments 0
You need to be logged in to leave comments. Login now