##// END OF EJS Templates
stricter types
M Bussonnier -
Show More
@@ -1,805 +1,828 b''
1 """
1 """
2 Utilities for working with strings and text.
2 Utilities for working with strings and text.
3
3
4 Inheritance diagram:
4 Inheritance diagram:
5
5
6 .. inheritance-diagram:: IPython.utils.text
6 .. inheritance-diagram:: IPython.utils.text
7 :parts: 3
7 :parts: 3
8 """
8 """
9
9
10 import os
10 import os
11 import re
11 import re
12 import string
12 import string
13 import sys
13 import textwrap
14 import textwrap
14 import warnings
15 import warnings
15 from string import Formatter
16 from string import Formatter
16 from pathlib import Path
17 from pathlib import Path
17
18
18 from typing import List, Dict, Tuple, Optional, cast
19 from typing import List, Dict, Tuple, Optional, cast, Sequence, Mapping, Any
20
21 if sys.version_info < (3, 12):
22 from typing_extensions import Self
23 else:
24 from typing import Self
19
25
20
26
21 class LSString(str):
27 class LSString(str):
22 """String derivative with a special access attributes.
28 """String derivative with a special access attributes.
23
29
24 These are normal strings, but with the special attributes:
30 These are normal strings, but with the special attributes:
25
31
26 .l (or .list) : value as list (split on newlines).
32 .l (or .list) : value as list (split on newlines).
27 .n (or .nlstr): original value (the string itself).
33 .n (or .nlstr): original value (the string itself).
28 .s (or .spstr): value as whitespace-separated string.
34 .s (or .spstr): value as whitespace-separated string.
29 .p (or .paths): list of path objects (requires path.py package)
35 .p (or .paths): list of path objects (requires path.py package)
30
36
31 Any values which require transformations are computed only once and
37 Any values which require transformations are computed only once and
32 cached.
38 cached.
33
39
34 Such strings are very useful to efficiently interact with the shell, which
40 Such strings are very useful to efficiently interact with the shell, which
35 typically only understands whitespace-separated options for commands."""
41 typically only understands whitespace-separated options for commands."""
36
42
37 def get_list(self):
43 __list: List[str]
44 __spstr: str
45 __paths: List[Path]
46
47 def get_list(self) -> List[str]:
38 try:
48 try:
39 return self.__list
49 return self.__list
40 except AttributeError:
50 except AttributeError:
41 self.__list = self.split('\n')
51 self.__list = self.split('\n')
42 return self.__list
52 return self.__list
43
53
44 l = list = property(get_list)
54 l = list = property(get_list)
45
55
46 def get_spstr(self):
56 def get_spstr(self) -> str:
47 try:
57 try:
48 return self.__spstr
58 return self.__spstr
49 except AttributeError:
59 except AttributeError:
50 self.__spstr = self.replace('\n',' ')
60 self.__spstr = self.replace('\n',' ')
51 return self.__spstr
61 return self.__spstr
52
62
53 s = spstr = property(get_spstr)
63 s = spstr = property(get_spstr)
54
64
55 def get_nlstr(self):
65 def get_nlstr(self) -> Self:
56 return self
66 return self
57
67
58 n = nlstr = property(get_nlstr)
68 n = nlstr = property(get_nlstr)
59
69
60 def get_paths(self):
70 def get_paths(self) -> List[Path]:
61 try:
71 try:
62 return self.__paths
72 return self.__paths
63 except AttributeError:
73 except AttributeError:
64 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
74 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
65 return self.__paths
75 return self.__paths
66
76
67 p = paths = property(get_paths)
77 p = paths = property(get_paths)
68
78
69 # FIXME: We need to reimplement type specific displayhook and then add this
79 # FIXME: We need to reimplement type specific displayhook and then add this
70 # back as a custom printer. This should also be moved outside utils into the
80 # back as a custom printer. This should also be moved outside utils into the
71 # core.
81 # core.
72
82
73 # def print_lsstring(arg):
83 # def print_lsstring(arg):
74 # """ Prettier (non-repr-like) and more informative printer for LSString """
84 # """ Prettier (non-repr-like) and more informative printer for LSString """
75 # print("LSString (.p, .n, .l, .s available). Value:")
85 # print("LSString (.p, .n, .l, .s available). Value:")
76 # print(arg)
86 # print(arg)
77 #
87 #
78 #
88 #
79 # print_lsstring = result_display.register(LSString)(print_lsstring)
89 # print_lsstring = result_display.register(LSString)(print_lsstring)
80
90
81
91
82 class SList(list):
92 class SList(list):
83 """List derivative with a special access attributes.
93 """List derivative with a special access attributes.
84
94
85 These are normal lists, but with the special attributes:
95 These are normal lists, but with the special attributes:
86
96
87 * .l (or .list) : value as list (the list itself).
97 * .l (or .list) : value as list (the list itself).
88 * .n (or .nlstr): value as a string, joined on newlines.
98 * .n (or .nlstr): value as a string, joined on newlines.
89 * .s (or .spstr): value as a string, joined on spaces.
99 * .s (or .spstr): value as a string, joined on spaces.
90 * .p (or .paths): list of path objects (requires path.py package)
100 * .p (or .paths): list of path objects (requires path.py package)
91
101
92 Any values which require transformations are computed only once and
102 Any values which require transformations are computed only once and
93 cached."""
103 cached."""
94
104
95 def get_list(self):
105 __spstr: str
106 __nlstr: str
107 __paths: List[Path]
108
109 def get_list(self) -> Self:
96 return self
110 return self
97
111
98 l = list = property(get_list)
112 l = list = property(get_list)
99
113
100 def get_spstr(self):
114 def get_spstr(self) -> str:
101 try:
115 try:
102 return self.__spstr
116 return self.__spstr
103 except AttributeError:
117 except AttributeError:
104 self.__spstr = ' '.join(self)
118 self.__spstr = ' '.join(self)
105 return self.__spstr
119 return self.__spstr
106
120
107 s = spstr = property(get_spstr)
121 s = spstr = property(get_spstr)
108
122
109 def get_nlstr(self):
123 def get_nlstr(self) -> str:
110 try:
124 try:
111 return self.__nlstr
125 return self.__nlstr
112 except AttributeError:
126 except AttributeError:
113 self.__nlstr = '\n'.join(self)
127 self.__nlstr = '\n'.join(self)
114 return self.__nlstr
128 return self.__nlstr
115
129
116 n = nlstr = property(get_nlstr)
130 n = nlstr = property(get_nlstr)
117
131
118 def get_paths(self):
132 def get_paths(self) -> List[Path]:
119 try:
133 try:
120 return self.__paths
134 return self.__paths
121 except AttributeError:
135 except AttributeError:
122 self.__paths = [Path(p) for p in self if os.path.exists(p)]
136 self.__paths = [Path(p) for p in self if os.path.exists(p)]
123 return self.__paths
137 return self.__paths
124
138
125 p = paths = property(get_paths)
139 p = paths = property(get_paths)
126
140
127 def grep(self, pattern, prune = False, field = None):
141 def grep(self, pattern, prune = False, field = None):
128 """ Return all strings matching 'pattern' (a regex or callable)
142 """ Return all strings matching 'pattern' (a regex or callable)
129
143
130 This is case-insensitive. If prune is true, return all items
144 This is case-insensitive. If prune is true, return all items
131 NOT matching the pattern.
145 NOT matching the pattern.
132
146
133 If field is specified, the match must occur in the specified
147 If field is specified, the match must occur in the specified
134 whitespace-separated field.
148 whitespace-separated field.
135
149
136 Examples::
150 Examples::
137
151
138 a.grep( lambda x: x.startswith('C') )
152 a.grep( lambda x: x.startswith('C') )
139 a.grep('Cha.*log', prune=1)
153 a.grep('Cha.*log', prune=1)
140 a.grep('chm', field=-1)
154 a.grep('chm', field=-1)
141 """
155 """
142
156
143 def match_target(s):
157 def match_target(s):
144 if field is None:
158 if field is None:
145 return s
159 return s
146 parts = s.split()
160 parts = s.split()
147 try:
161 try:
148 tgt = parts[field]
162 tgt = parts[field]
149 return tgt
163 return tgt
150 except IndexError:
164 except IndexError:
151 return ""
165 return ""
152
166
153 if isinstance(pattern, str):
167 if isinstance(pattern, str):
154 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
168 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
155 else:
169 else:
156 pred = pattern
170 pred = pattern
157 if not prune:
171 if not prune:
158 return SList([el for el in self if pred(match_target(el))])
172 return SList([el for el in self if pred(match_target(el))])
159 else:
173 else:
160 return SList([el for el in self if not pred(match_target(el))])
174 return SList([el for el in self if not pred(match_target(el))])
161
175
162 def fields(self, *fields):
176 def fields(self, *fields):
163 """ Collect whitespace-separated fields from string list
177 """ Collect whitespace-separated fields from string list
164
178
165 Allows quick awk-like usage of string lists.
179 Allows quick awk-like usage of string lists.
166
180
167 Example data (in var a, created by 'a = !ls -l')::
181 Example data (in var a, created by 'a = !ls -l')::
168
182
169 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
183 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
170 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
184 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
171
185
172 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
186 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
173 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
187 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
174 (note the joining by space).
188 (note the joining by space).
175 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
189 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
176
190
177 IndexErrors are ignored.
191 IndexErrors are ignored.
178
192
179 Without args, fields() just split()'s the strings.
193 Without args, fields() just split()'s the strings.
180 """
194 """
181 if len(fields) == 0:
195 if len(fields) == 0:
182 return [el.split() for el in self]
196 return [el.split() for el in self]
183
197
184 res = SList()
198 res = SList()
185 for el in [f.split() for f in self]:
199 for el in [f.split() for f in self]:
186 lineparts = []
200 lineparts = []
187
201
188 for fd in fields:
202 for fd in fields:
189 try:
203 try:
190 lineparts.append(el[fd])
204 lineparts.append(el[fd])
191 except IndexError:
205 except IndexError:
192 pass
206 pass
193 if lineparts:
207 if lineparts:
194 res.append(" ".join(lineparts))
208 res.append(" ".join(lineparts))
195
209
196 return res
210 return res
197
211
198 def sort(self,field= None, nums = False):
212 def sort(self,field= None, nums = False):
199 """ sort by specified fields (see fields())
213 """ sort by specified fields (see fields())
200
214
201 Example::
215 Example::
202
216
203 a.sort(1, nums = True)
217 a.sort(1, nums = True)
204
218
205 Sorts a by second field, in numerical order (so that 21 > 3)
219 Sorts a by second field, in numerical order (so that 21 > 3)
206
220
207 """
221 """
208
222
209 #decorate, sort, undecorate
223 #decorate, sort, undecorate
210 if field is not None:
224 if field is not None:
211 dsu = [[SList([line]).fields(field), line] for line in self]
225 dsu = [[SList([line]).fields(field), line] for line in self]
212 else:
226 else:
213 dsu = [[line, line] for line in self]
227 dsu = [[line, line] for line in self]
214 if nums:
228 if nums:
215 for i in range(len(dsu)):
229 for i in range(len(dsu)):
216 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
230 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
217 try:
231 try:
218 n = int(numstr)
232 n = int(numstr)
219 except ValueError:
233 except ValueError:
220 n = 0
234 n = 0
221 dsu[i][0] = n
235 dsu[i][0] = n
222
236
223
237
224 dsu.sort()
238 dsu.sort()
225 return SList([t[1] for t in dsu])
239 return SList([t[1] for t in dsu])
226
240
227
241
228 # FIXME: We need to reimplement type specific displayhook and then add this
242 # FIXME: We need to reimplement type specific displayhook and then add this
229 # back as a custom printer. This should also be moved outside utils into the
243 # back as a custom printer. This should also be moved outside utils into the
230 # core.
244 # core.
231
245
232 # def print_slist(arg):
246 # def print_slist(arg):
233 # """ Prettier (non-repr-like) and more informative printer for SList """
247 # """ Prettier (non-repr-like) and more informative printer for SList """
234 # print("SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):")
248 # print("SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):")
235 # if hasattr(arg, 'hideonce') and arg.hideonce:
249 # if hasattr(arg, 'hideonce') and arg.hideonce:
236 # arg.hideonce = False
250 # arg.hideonce = False
237 # return
251 # return
238 #
252 #
239 # nlprint(arg) # This was a nested list printer, now removed.
253 # nlprint(arg) # This was a nested list printer, now removed.
240 #
254 #
241 # print_slist = result_display.register(SList)(print_slist)
255 # print_slist = result_display.register(SList)(print_slist)
242
256
243
257
244 def indent(instr,nspaces=4, ntabs=0, flatten=False):
258 def indent(instr,nspaces=4, ntabs=0, flatten=False):
245 """Indent a string a given number of spaces or tabstops.
259 """Indent a string a given number of spaces or tabstops.
246
260
247 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
261 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
248
262
249 Parameters
263 Parameters
250 ----------
264 ----------
251 instr : basestring
265 instr : basestring
252 The string to be indented.
266 The string to be indented.
253 nspaces : int (default: 4)
267 nspaces : int (default: 4)
254 The number of spaces to be indented.
268 The number of spaces to be indented.
255 ntabs : int (default: 0)
269 ntabs : int (default: 0)
256 The number of tabs to be indented.
270 The number of tabs to be indented.
257 flatten : bool (default: False)
271 flatten : bool (default: False)
258 Whether to scrub existing indentation. If True, all lines will be
272 Whether to scrub existing indentation. If True, all lines will be
259 aligned to the same indentation. If False, existing indentation will
273 aligned to the same indentation. If False, existing indentation will
260 be strictly increased.
274 be strictly increased.
261
275
262 Returns
276 Returns
263 -------
277 -------
264 str|unicode : string indented by ntabs and nspaces.
278 str|unicode : string indented by ntabs and nspaces.
265
279
266 """
280 """
267 if instr is None:
281 if instr is None:
268 return
282 return
269 ind = '\t'*ntabs+' '*nspaces
283 ind = '\t'*ntabs+' '*nspaces
270 if flatten:
284 if flatten:
271 pat = re.compile(r'^\s*', re.MULTILINE)
285 pat = re.compile(r'^\s*', re.MULTILINE)
272 else:
286 else:
273 pat = re.compile(r'^', re.MULTILINE)
287 pat = re.compile(r'^', re.MULTILINE)
274 outstr = re.sub(pat, ind, instr)
288 outstr = re.sub(pat, ind, instr)
275 if outstr.endswith(os.linesep+ind):
289 if outstr.endswith(os.linesep+ind):
276 return outstr[:-len(ind)]
290 return outstr[:-len(ind)]
277 else:
291 else:
278 return outstr
292 return outstr
279
293
280
294
281 def list_strings(arg):
295 def list_strings(arg):
282 """Always return a list of strings, given a string or list of strings
296 """Always return a list of strings, given a string or list of strings
283 as input.
297 as input.
284
298
285 Examples
299 Examples
286 --------
300 --------
287 ::
301 ::
288
302
289 In [7]: list_strings('A single string')
303 In [7]: list_strings('A single string')
290 Out[7]: ['A single string']
304 Out[7]: ['A single string']
291
305
292 In [8]: list_strings(['A single string in a list'])
306 In [8]: list_strings(['A single string in a list'])
293 Out[8]: ['A single string in a list']
307 Out[8]: ['A single string in a list']
294
308
295 In [9]: list_strings(['A','list','of','strings'])
309 In [9]: list_strings(['A','list','of','strings'])
296 Out[9]: ['A', 'list', 'of', 'strings']
310 Out[9]: ['A', 'list', 'of', 'strings']
297 """
311 """
298
312
299 if isinstance(arg, str):
313 if isinstance(arg, str):
300 return [arg]
314 return [arg]
301 else:
315 else:
302 return arg
316 return arg
303
317
304
318
305 def marquee(txt='',width=78,mark='*'):
319 def marquee(txt='',width=78,mark='*'):
306 """Return the input string centered in a 'marquee'.
320 """Return the input string centered in a 'marquee'.
307
321
308 Examples
322 Examples
309 --------
323 --------
310 ::
324 ::
311
325
312 In [16]: marquee('A test',40)
326 In [16]: marquee('A test',40)
313 Out[16]: '**************** A test ****************'
327 Out[16]: '**************** A test ****************'
314
328
315 In [17]: marquee('A test',40,'-')
329 In [17]: marquee('A test',40,'-')
316 Out[17]: '---------------- A test ----------------'
330 Out[17]: '---------------- A test ----------------'
317
331
318 In [18]: marquee('A test',40,' ')
332 In [18]: marquee('A test',40,' ')
319 Out[18]: ' A test '
333 Out[18]: ' A test '
320
334
321 """
335 """
322 if not txt:
336 if not txt:
323 return (mark*width)[:width]
337 return (mark*width)[:width]
324 nmark = (width-len(txt)-2)//len(mark)//2
338 nmark = (width-len(txt)-2)//len(mark)//2
325 if nmark < 0: nmark =0
339 if nmark < 0: nmark =0
326 marks = mark*nmark
340 marks = mark*nmark
327 return '%s %s %s' % (marks,txt,marks)
341 return '%s %s %s' % (marks,txt,marks)
328
342
329
343
330 ini_spaces_re = re.compile(r'^(\s+)')
344 ini_spaces_re = re.compile(r'^(\s+)')
331
345
332 def num_ini_spaces(strng):
346 def num_ini_spaces(strng):
333 """Return the number of initial spaces in a string"""
347 """Return the number of initial spaces in a string"""
334 warnings.warn(
348 warnings.warn(
335 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
349 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
336 "It is considered fro removal in in future version. "
350 "It is considered fro removal in in future version. "
337 "Please open an issue if you believe it should be kept.",
351 "Please open an issue if you believe it should be kept.",
338 stacklevel=2,
352 stacklevel=2,
339 category=PendingDeprecationWarning,
353 category=PendingDeprecationWarning,
340 )
354 )
341 ini_spaces = ini_spaces_re.match(strng)
355 ini_spaces = ini_spaces_re.match(strng)
342 if ini_spaces:
356 if ini_spaces:
343 return ini_spaces.end()
357 return ini_spaces.end()
344 else:
358 else:
345 return 0
359 return 0
346
360
347
361
348 def format_screen(strng):
362 def format_screen(strng):
349 """Format a string for screen printing.
363 """Format a string for screen printing.
350
364
351 This removes some latex-type format codes."""
365 This removes some latex-type format codes."""
352 # Paragraph continue
366 # Paragraph continue
353 par_re = re.compile(r'\\$',re.MULTILINE)
367 par_re = re.compile(r'\\$',re.MULTILINE)
354 strng = par_re.sub('',strng)
368 strng = par_re.sub('',strng)
355 return strng
369 return strng
356
370
357
371
358 def dedent(text: str) -> str:
372 def dedent(text: str) -> str:
359 """Equivalent of textwrap.dedent that ignores unindented first line.
373 """Equivalent of textwrap.dedent that ignores unindented first line.
360
374
361 This means it will still dedent strings like:
375 This means it will still dedent strings like:
362 '''foo
376 '''foo
363 is a bar
377 is a bar
364 '''
378 '''
365
379
366 For use in wrap_paragraphs.
380 For use in wrap_paragraphs.
367 """
381 """
368
382
369 if text.startswith('\n'):
383 if text.startswith('\n'):
370 # text starts with blank line, don't ignore the first line
384 # text starts with blank line, don't ignore the first line
371 return textwrap.dedent(text)
385 return textwrap.dedent(text)
372
386
373 # split first line
387 # split first line
374 splits = text.split('\n',1)
388 splits = text.split('\n',1)
375 if len(splits) == 1:
389 if len(splits) == 1:
376 # only one line
390 # only one line
377 return textwrap.dedent(text)
391 return textwrap.dedent(text)
378
392
379 first, rest = splits
393 first, rest = splits
380 # dedent everything but the first line
394 # dedent everything but the first line
381 rest = textwrap.dedent(rest)
395 rest = textwrap.dedent(rest)
382 return '\n'.join([first, rest])
396 return '\n'.join([first, rest])
383
397
384
398
385 def wrap_paragraphs(text, ncols=80):
399 def wrap_paragraphs(text, ncols=80):
386 """Wrap multiple paragraphs to fit a specified width.
400 """Wrap multiple paragraphs to fit a specified width.
387
401
388 This is equivalent to textwrap.wrap, but with support for multiple
402 This is equivalent to textwrap.wrap, but with support for multiple
389 paragraphs, as separated by empty lines.
403 paragraphs, as separated by empty lines.
390
404
391 Returns
405 Returns
392 -------
406 -------
393 list of complete paragraphs, wrapped to fill `ncols` columns.
407 list of complete paragraphs, wrapped to fill `ncols` columns.
394 """
408 """
395 warnings.warn(
409 warnings.warn(
396 "`wrap_paragraphs` is Pending Deprecation since IPython 8.17."
410 "`wrap_paragraphs` is Pending Deprecation since IPython 8.17."
397 "It is considered fro removal in in future version. "
411 "It is considered fro removal in in future version. "
398 "Please open an issue if you believe it should be kept.",
412 "Please open an issue if you believe it should be kept.",
399 stacklevel=2,
413 stacklevel=2,
400 category=PendingDeprecationWarning,
414 category=PendingDeprecationWarning,
401 )
415 )
402 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
416 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
403 text = dedent(text).strip()
417 text = dedent(text).strip()
404 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
418 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
405 out_ps = []
419 out_ps = []
406 indent_re = re.compile(r'\n\s+', re.MULTILINE)
420 indent_re = re.compile(r'\n\s+', re.MULTILINE)
407 for p in paragraphs:
421 for p in paragraphs:
408 # presume indentation that survives dedent is meaningful formatting,
422 # presume indentation that survives dedent is meaningful formatting,
409 # so don't fill unless text is flush.
423 # so don't fill unless text is flush.
410 if indent_re.search(p) is None:
424 if indent_re.search(p) is None:
411 # wrap paragraph
425 # wrap paragraph
412 p = textwrap.fill(p, ncols)
426 p = textwrap.fill(p, ncols)
413 out_ps.append(p)
427 out_ps.append(p)
414 return out_ps
428 return out_ps
415
429
416
430
417 def strip_email_quotes(text):
431 def strip_email_quotes(text):
418 """Strip leading email quotation characters ('>').
432 """Strip leading email quotation characters ('>').
419
433
420 Removes any combination of leading '>' interspersed with whitespace that
434 Removes any combination of leading '>' interspersed with whitespace that
421 appears *identically* in all lines of the input text.
435 appears *identically* in all lines of the input text.
422
436
423 Parameters
437 Parameters
424 ----------
438 ----------
425 text : str
439 text : str
426
440
427 Examples
441 Examples
428 --------
442 --------
429
443
430 Simple uses::
444 Simple uses::
431
445
432 In [2]: strip_email_quotes('> > text')
446 In [2]: strip_email_quotes('> > text')
433 Out[2]: 'text'
447 Out[2]: 'text'
434
448
435 In [3]: strip_email_quotes('> > text\\n> > more')
449 In [3]: strip_email_quotes('> > text\\n> > more')
436 Out[3]: 'text\\nmore'
450 Out[3]: 'text\\nmore'
437
451
438 Note how only the common prefix that appears in all lines is stripped::
452 Note how only the common prefix that appears in all lines is stripped::
439
453
440 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
454 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
441 Out[4]: '> text\\n> more\\nmore...'
455 Out[4]: '> text\\n> more\\nmore...'
442
456
443 So if any line has no quote marks ('>'), then none are stripped from any
457 So if any line has no quote marks ('>'), then none are stripped from any
444 of them ::
458 of them ::
445
459
446 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
460 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
447 Out[5]: '> > text\\n> > more\\nlast different'
461 Out[5]: '> > text\\n> > more\\nlast different'
448 """
462 """
449 lines = text.splitlines()
463 lines = text.splitlines()
450 strip_len = 0
464 strip_len = 0
451
465
452 for characters in zip(*lines):
466 for characters in zip(*lines):
453 # Check if all characters in this position are the same
467 # Check if all characters in this position are the same
454 if len(set(characters)) > 1:
468 if len(set(characters)) > 1:
455 break
469 break
456 prefix_char = characters[0]
470 prefix_char = characters[0]
457
471
458 if prefix_char in string.whitespace or prefix_char == ">":
472 if prefix_char in string.whitespace or prefix_char == ">":
459 strip_len += 1
473 strip_len += 1
460 else:
474 else:
461 break
475 break
462
476
463 text = "\n".join([ln[strip_len:] for ln in lines])
477 text = "\n".join([ln[strip_len:] for ln in lines])
464 return text
478 return text
465
479
466
480
467 def strip_ansi(source):
481 def strip_ansi(source):
468 """
482 """
469 Remove ansi escape codes from text.
483 Remove ansi escape codes from text.
470
484
471 Parameters
485 Parameters
472 ----------
486 ----------
473 source : str
487 source : str
474 Source to remove the ansi from
488 Source to remove the ansi from
475 """
489 """
476 warnings.warn(
490 warnings.warn(
477 "`strip_ansi` is Pending Deprecation since IPython 8.17."
491 "`strip_ansi` is Pending Deprecation since IPython 8.17."
478 "It is considered fro removal in in future version. "
492 "It is considered fro removal in in future version. "
479 "Please open an issue if you believe it should be kept.",
493 "Please open an issue if you believe it should be kept.",
480 stacklevel=2,
494 stacklevel=2,
481 category=PendingDeprecationWarning,
495 category=PendingDeprecationWarning,
482 )
496 )
483
497
484 return re.sub(r'\033\[(\d|;)+?m', '', source)
498 return re.sub(r'\033\[(\d|;)+?m', '', source)
485
499
486
500
487 class EvalFormatter(Formatter):
501 class EvalFormatter(Formatter):
488 """A String Formatter that allows evaluation of simple expressions.
502 """A String Formatter that allows evaluation of simple expressions.
489
503
490 Note that this version interprets a `:` as specifying a format string (as per
504 Note that this version interprets a `:` as specifying a format string (as per
491 standard string formatting), so if slicing is required, you must explicitly
505 standard string formatting), so if slicing is required, you must explicitly
492 create a slice.
506 create a slice.
493
507
494 This is to be used in templating cases, such as the parallel batch
508 This is to be used in templating cases, such as the parallel batch
495 script templates, where simple arithmetic on arguments is useful.
509 script templates, where simple arithmetic on arguments is useful.
496
510
497 Examples
511 Examples
498 --------
512 --------
499 ::
513 ::
500
514
501 In [1]: f = EvalFormatter()
515 In [1]: f = EvalFormatter()
502 In [2]: f.format('{n//4}', n=8)
516 In [2]: f.format('{n//4}', n=8)
503 Out[2]: '2'
517 Out[2]: '2'
504
518
505 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
519 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
506 Out[3]: 'll'
520 Out[3]: 'll'
507 """
521 """
508 def get_field(self, name, args, kwargs):
522 def get_field(self, name, args, kwargs):
509 v = eval(name, kwargs)
523 v = eval(name, kwargs)
510 return v, name
524 return v, name
511
525
512 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
526 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
513 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
527 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
514 # above, it should be possible to remove FullEvalFormatter.
528 # above, it should be possible to remove FullEvalFormatter.
515
529
516 class FullEvalFormatter(Formatter):
530 class FullEvalFormatter(Formatter):
517 """A String Formatter that allows evaluation of simple expressions.
531 """A String Formatter that allows evaluation of simple expressions.
518
532
519 Any time a format key is not found in the kwargs,
533 Any time a format key is not found in the kwargs,
520 it will be tried as an expression in the kwargs namespace.
534 it will be tried as an expression in the kwargs namespace.
521
535
522 Note that this version allows slicing using [1:2], so you cannot specify
536 Note that this version allows slicing using [1:2], so you cannot specify
523 a format string. Use :class:`EvalFormatter` to permit format strings.
537 a format string. Use :class:`EvalFormatter` to permit format strings.
524
538
525 Examples
539 Examples
526 --------
540 --------
527 ::
541 ::
528
542
529 In [1]: f = FullEvalFormatter()
543 In [1]: f = FullEvalFormatter()
530 In [2]: f.format('{n//4}', n=8)
544 In [2]: f.format('{n//4}', n=8)
531 Out[2]: '2'
545 Out[2]: '2'
532
546
533 In [3]: f.format('{list(range(5))[2:4]}')
547 In [3]: f.format('{list(range(5))[2:4]}')
534 Out[3]: '[2, 3]'
548 Out[3]: '[2, 3]'
535
549
536 In [4]: f.format('{3*2}')
550 In [4]: f.format('{3*2}')
537 Out[4]: '6'
551 Out[4]: '6'
538 """
552 """
539 # copied from Formatter._vformat with minor changes to allow eval
553 # copied from Formatter._vformat with minor changes to allow eval
540 # and replace the format_spec code with slicing
554 # and replace the format_spec code with slicing
541 def vformat(self, format_string: str, args, kwargs) -> str:
555 def vformat(
556 self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]
557 ) -> str:
542 result = []
558 result = []
543 conversion: Optional[str]
559 conversion: Optional[str]
544 for literal_text, field_name, format_spec, conversion in self.parse(
560 for literal_text, field_name, format_spec, conversion in self.parse(
545 format_string
561 format_string
546 ):
562 ):
547 # output the literal text
563 # output the literal text
548 if literal_text:
564 if literal_text:
549 result.append(literal_text)
565 result.append(literal_text)
550
566
551 # if there's a field, output it
567 # if there's a field, output it
552 if field_name is not None:
568 if field_name is not None:
553 # this is some markup, find the object and do
569 # this is some markup, find the object and do
554 # the formatting
570 # the formatting
555
571
556 if format_spec:
572 if format_spec:
557 # override format spec, to allow slicing:
573 # override format spec, to allow slicing:
558 field_name = ':'.join([field_name, format_spec])
574 field_name = ':'.join([field_name, format_spec])
559
575
560 # eval the contents of the field for the object
576 # eval the contents of the field for the object
561 # to be formatted
577 # to be formatted
562 obj = eval(field_name, kwargs)
578 obj = eval(field_name, dict(kwargs))
563
579
564 # do any conversion on the resulting object
580 # do any conversion on the resulting object
565 # type issue in typeshed, fined in https://github.com/python/typeshed/pull/11377
581 # type issue in typeshed, fined in https://github.com/python/typeshed/pull/11377
566 obj = self.convert_field(obj, conversion) # type: ignore[arg-type]
582 obj = self.convert_field(obj, conversion) # type: ignore[arg-type]
567
583
568 # format the object and append to the result
584 # format the object and append to the result
569 result.append(self.format_field(obj, ''))
585 result.append(self.format_field(obj, ''))
570
586
571 return ''.join(result)
587 return ''.join(result)
572
588
573
589
574 class DollarFormatter(FullEvalFormatter):
590 class DollarFormatter(FullEvalFormatter):
575 """Formatter allowing Itpl style $foo replacement, for names and attribute
591 """Formatter allowing Itpl style $foo replacement, for names and attribute
576 access only. Standard {foo} replacement also works, and allows full
592 access only. Standard {foo} replacement also works, and allows full
577 evaluation of its arguments.
593 evaluation of its arguments.
578
594
579 Examples
595 Examples
580 --------
596 --------
581 ::
597 ::
582
598
583 In [1]: f = DollarFormatter()
599 In [1]: f = DollarFormatter()
584 In [2]: f.format('{n//4}', n=8)
600 In [2]: f.format('{n//4}', n=8)
585 Out[2]: '2'
601 Out[2]: '2'
586
602
587 In [3]: f.format('23 * 76 is $result', result=23*76)
603 In [3]: f.format('23 * 76 is $result', result=23*76)
588 Out[3]: '23 * 76 is 1748'
604 Out[3]: '23 * 76 is 1748'
589
605
590 In [4]: f.format('$a or {b}', a=1, b=2)
606 In [4]: f.format('$a or {b}', a=1, b=2)
591 Out[4]: '1 or 2'
607 Out[4]: '1 or 2'
592 """
608 """
593 _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
609 _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
594 def parse(self, fmt_string):
610 def parse(self, fmt_string):
595 for literal_txt, field_name, format_spec, conversion \
611 for literal_txt, field_name, format_spec, conversion \
596 in Formatter.parse(self, fmt_string):
612 in Formatter.parse(self, fmt_string):
597
613
598 # Find $foo patterns in the literal text.
614 # Find $foo patterns in the literal text.
599 continue_from = 0
615 continue_from = 0
600 txt = ""
616 txt = ""
601 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
617 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
602 new_txt, new_field = m.group(1,2)
618 new_txt, new_field = m.group(1,2)
603 # $$foo --> $foo
619 # $$foo --> $foo
604 if new_field.startswith("$"):
620 if new_field.startswith("$"):
605 txt += new_txt + new_field
621 txt += new_txt + new_field
606 else:
622 else:
607 yield (txt + new_txt, new_field, "", None)
623 yield (txt + new_txt, new_field, "", None)
608 txt = ""
624 txt = ""
609 continue_from = m.end()
625 continue_from = m.end()
610
626
611 # Re-yield the {foo} style pattern
627 # Re-yield the {foo} style pattern
612 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
628 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
613
629
614 def __repr__(self):
630 def __repr__(self):
615 return "<DollarFormatter>"
631 return "<DollarFormatter>"
616
632
617 #-----------------------------------------------------------------------------
633 #-----------------------------------------------------------------------------
618 # Utils to columnize a list of string
634 # Utils to columnize a list of string
619 #-----------------------------------------------------------------------------
635 #-----------------------------------------------------------------------------
620
636
621 def _col_chunks(l, max_rows, row_first=False):
637 def _col_chunks(l, max_rows, row_first=False):
622 """Yield successive max_rows-sized column chunks from l."""
638 """Yield successive max_rows-sized column chunks from l."""
623 if row_first:
639 if row_first:
624 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
640 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
625 for i in range(ncols):
641 for i in range(ncols):
626 yield [l[j] for j in range(i, len(l), ncols)]
642 yield [l[j] for j in range(i, len(l), ncols)]
627 else:
643 else:
628 for i in range(0, len(l), max_rows):
644 for i in range(0, len(l), max_rows):
629 yield l[i:(i + max_rows)]
645 yield l[i:(i + max_rows)]
630
646
631
647
632 def _find_optimal(rlist, row_first: bool, separator_size: int, displaywidth: int):
648 def _find_optimal(
649 rlist: List[str], row_first: bool, separator_size: int, displaywidth: int
650 ) -> Dict[str, Any]:
633 """Calculate optimal info to columnize a list of string"""
651 """Calculate optimal info to columnize a list of string"""
634 for max_rows in range(1, len(rlist) + 1):
652 for max_rows in range(1, len(rlist) + 1):
635 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
653 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
636 sumlength = sum(col_widths)
654 sumlength = sum(col_widths)
637 ncols = len(col_widths)
655 ncols = len(col_widths)
638 if sumlength + separator_size * (ncols - 1) <= displaywidth:
656 if sumlength + separator_size * (ncols - 1) <= displaywidth:
639 break
657 break
640 return {'num_columns': ncols,
658 return {'num_columns': ncols,
641 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
659 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
642 'max_rows': max_rows,
660 'max_rows': max_rows,
643 'column_widths': col_widths
661 'column_widths': col_widths
644 }
662 }
645
663
646
664
647 def _get_or_default(mylist, i, default=None):
665 def _get_or_default(mylist, i, default=None):
648 """return list item number, or default if don't exist"""
666 """return list item number, or default if don't exist"""
649 if i >= len(mylist):
667 if i >= len(mylist):
650 return default
668 return default
651 else :
669 else :
652 return mylist[i]
670 return mylist[i]
653
671
654
672
655 def compute_item_matrix(
673 def compute_item_matrix(
656 items, row_first: bool = False, empty=None, *, separator_size=2, displaywidth=80
674 items: List[str],
675 row_first: bool = False,
676 empty: Optional[str] = None,
677 *,
678 separator_size: int = 2,
679 displaywidth: int = 80,
657 ) -> Tuple[List[List[int]], Dict[str, int]]:
680 ) -> Tuple[List[List[int]], Dict[str, int]]:
658 """Returns a nested list, and info to columnize items
681 """Returns a nested list, and info to columnize items
659
682
660 Parameters
683 Parameters
661 ----------
684 ----------
662 items
685 items
663 list of strings to columize
686 list of strings to columize
664 row_first : (default False)
687 row_first : (default False)
665 Whether to compute columns for a row-first matrix instead of
688 Whether to compute columns for a row-first matrix instead of
666 column-first (default).
689 column-first (default).
667 empty : (default None)
690 empty : (default None)
668 default value to fill list if needed
691 default value to fill list if needed
669 separator_size : int (default=2)
692 separator_size : int (default=2)
670 How much characters will be used as a separation between each columns.
693 How much characters will be used as a separation between each columns.
671 displaywidth : int (default=80)
694 displaywidth : int (default=80)
672 The width of the area onto which the columns should enter
695 The width of the area onto which the columns should enter
673
696
674 Returns
697 Returns
675 -------
698 -------
676 strings_matrix
699 strings_matrix
677 nested list of string, the outer most list contains as many list as
700 nested list of string, the outer most list contains as many list as
678 rows, the innermost lists have each as many element as columns. If the
701 rows, the innermost lists have each as many element as columns. If the
679 total number of elements in `items` does not equal the product of
702 total number of elements in `items` does not equal the product of
680 rows*columns, the last element of some lists are filled with `None`.
703 rows*columns, the last element of some lists are filled with `None`.
681 dict_info
704 dict_info
682 some info to make columnize easier:
705 some info to make columnize easier:
683
706
684 num_columns
707 num_columns
685 number of columns
708 number of columns
686 max_rows
709 max_rows
687 maximum number of rows (final number may be less)
710 maximum number of rows (final number may be less)
688 column_widths
711 column_widths
689 list of with of each columns
712 list of with of each columns
690 optimal_separator_width
713 optimal_separator_width
691 best separator width between columns
714 best separator width between columns
692
715
693 Examples
716 Examples
694 --------
717 --------
695 ::
718 ::
696
719
697 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
720 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
698 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
721 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
699 In [3]: list
722 In [3]: list
700 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
723 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
701 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
724 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
702 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
725 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
703 Out[5]: True
726 Out[5]: True
704 """
727 """
705 warnings.warn(
728 warnings.warn(
706 "`compute_item_matrix` is Pending Deprecation since IPython 8.17."
729 "`compute_item_matrix` is Pending Deprecation since IPython 8.17."
707 "It is considered fro removal in in future version. "
730 "It is considered fro removal in in future version. "
708 "Please open an issue if you believe it should be kept.",
731 "Please open an issue if you believe it should be kept.",
709 stacklevel=2,
732 stacklevel=2,
710 category=PendingDeprecationWarning,
733 category=PendingDeprecationWarning,
711 )
734 )
712 info = _find_optimal(
735 info = _find_optimal(
713 list(map(len, items)),
736 list(map(len, items)), # type: ignore[arg-type]
714 row_first,
737 row_first,
715 separator_size=separator_size,
738 separator_size=separator_size,
716 displaywidth=displaywidth,
739 displaywidth=displaywidth,
717 )
740 )
718 nrow, ncol = info["max_rows"], info["num_columns"]
741 nrow, ncol = info["max_rows"], info["num_columns"]
719 if row_first:
742 if row_first:
720 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
743 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
721 else:
744 else:
722 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
745 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
723
746
724
747
725 def columnize(
748 def columnize(
726 items: List[str],
749 items: List[str],
727 row_first: bool = False,
750 row_first: bool = False,
728 separator: str = " ",
751 separator: str = " ",
729 displaywidth: int = 80,
752 displaywidth: int = 80,
730 spread: bool = False,
753 spread: bool = False,
731 ):
754 ) -> str:
732 """Transform a list of strings into a single string with columns.
755 """Transform a list of strings into a single string with columns.
733
756
734 Parameters
757 Parameters
735 ----------
758 ----------
736 items : sequence of strings
759 items : sequence of strings
737 The strings to process.
760 The strings to process.
738 row_first : (default False)
761 row_first : (default False)
739 Whether to compute columns for a row-first matrix instead of
762 Whether to compute columns for a row-first matrix instead of
740 column-first (default).
763 column-first (default).
741 separator : str, optional [default is two spaces]
764 separator : str, optional [default is two spaces]
742 The string that separates columns.
765 The string that separates columns.
743 displaywidth : int, optional [default is 80]
766 displaywidth : int, optional [default is 80]
744 Width of the display in number of characters.
767 Width of the display in number of characters.
745
768
746 Returns
769 Returns
747 -------
770 -------
748 The formatted string.
771 The formatted string.
749 """
772 """
750 warnings.warn(
773 warnings.warn(
751 "`columnize` is Pending Deprecation since IPython 8.17."
774 "`columnize` is Pending Deprecation since IPython 8.17."
752 "It is considered for removal in future versions. "
775 "It is considered for removal in future versions. "
753 "Please open an issue if you believe it should be kept.",
776 "Please open an issue if you believe it should be kept.",
754 stacklevel=2,
777 stacklevel=2,
755 category=PendingDeprecationWarning,
778 category=PendingDeprecationWarning,
756 )
779 )
757 if not items:
780 if not items:
758 return "\n"
781 return "\n"
759 matrix: List[List[int]]
782 matrix: List[List[int]]
760 matrix, info = compute_item_matrix(
783 matrix, info = compute_item_matrix(
761 items,
784 items,
762 row_first=row_first,
785 row_first=row_first,
763 separator_size=len(separator),
786 separator_size=len(separator),
764 displaywidth=displaywidth,
787 displaywidth=displaywidth,
765 )
788 )
766 if spread:
789 if spread:
767 separator = separator.ljust(int(info["optimal_separator_width"]))
790 separator = separator.ljust(int(info["optimal_separator_width"]))
768 fmatrix: List[filter[int]] = [filter(None, x) for x in matrix]
791 fmatrix: List[filter[int]] = [filter(None, x) for x in matrix]
769 sjoin = lambda x: separator.join(
792 sjoin = lambda x: separator.join(
770 [y.ljust(w, " ") for y, w in zip(x, cast(List[int], info["column_widths"]))]
793 [y.ljust(w, " ") for y, w in zip(x, cast(List[int], info["column_widths"]))]
771 )
794 )
772 return "\n".join(map(sjoin, fmatrix)) + "\n"
795 return "\n".join(map(sjoin, fmatrix)) + "\n"
773
796
774
797
775 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
798 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
776 """
799 """
777 Return a string with a natural enumeration of items
800 Return a string with a natural enumeration of items
778
801
779 >>> get_text_list(['a', 'b', 'c', 'd'])
802 >>> get_text_list(['a', 'b', 'c', 'd'])
780 'a, b, c and d'
803 'a, b, c and d'
781 >>> get_text_list(['a', 'b', 'c'], ' or ')
804 >>> get_text_list(['a', 'b', 'c'], ' or ')
782 'a, b or c'
805 'a, b or c'
783 >>> get_text_list(['a', 'b', 'c'], ', ')
806 >>> get_text_list(['a', 'b', 'c'], ', ')
784 'a, b, c'
807 'a, b, c'
785 >>> get_text_list(['a', 'b'], ' or ')
808 >>> get_text_list(['a', 'b'], ' or ')
786 'a or b'
809 'a or b'
787 >>> get_text_list(['a'])
810 >>> get_text_list(['a'])
788 'a'
811 'a'
789 >>> get_text_list([])
812 >>> get_text_list([])
790 ''
813 ''
791 >>> get_text_list(['a', 'b'], wrap_item_with="`")
814 >>> get_text_list(['a', 'b'], wrap_item_with="`")
792 '`a` and `b`'
815 '`a` and `b`'
793 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
816 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
794 'a + b + c = d'
817 'a + b + c = d'
795 """
818 """
796 if len(list_) == 0:
819 if len(list_) == 0:
797 return ''
820 return ''
798 if wrap_item_with:
821 if wrap_item_with:
799 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
822 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
800 item in list_]
823 item in list_]
801 if len(list_) == 1:
824 if len(list_) == 1:
802 return list_[0]
825 return list_[0]
803 return '%s%s%s' % (
826 return '%s%s%s' % (
804 sep.join(i for i in list_[:-1]),
827 sep.join(i for i in list_[:-1]),
805 last_sep, list_[-1])
828 last_sep, list_[-1])
@@ -1,210 +1,368 b''
1 [build-system]
1 [build-system]
2 requires = ["setuptools>=61.2"]
2 requires = ["setuptools>=61.2"]
3 # We need access to the 'setupbase' module at build time.
3 # We need access to the 'setupbase' module at build time.
4 # Hence we declare a custom build backend.
4 # Hence we declare a custom build backend.
5 build-backend = "_build_meta" # just re-exports setuptools.build_meta definitions
5 build-backend = "_build_meta" # just re-exports setuptools.build_meta definitions
6 backend-path = ["."]
6 backend-path = ["."]
7
7
8 [project]
8 [project]
9 name = "ipython"
9 name = "ipython"
10 description = "IPython: Productive Interactive Computing"
10 description = "IPython: Productive Interactive Computing"
11 keywords = ["Interactive", "Interpreter", "Shell", "Embedding"]
11 keywords = ["Interactive", "Interpreter", "Shell", "Embedding"]
12 classifiers = [
12 classifiers = [
13 "Framework :: IPython",
13 "Framework :: IPython",
14 "Framework :: Jupyter",
14 "Framework :: Jupyter",
15 "Intended Audience :: Developers",
15 "Intended Audience :: Developers",
16 "Intended Audience :: Science/Research",
16 "Intended Audience :: Science/Research",
17 "License :: OSI Approved :: BSD License",
17 "License :: OSI Approved :: BSD License",
18 "Programming Language :: Python",
18 "Programming Language :: Python",
19 "Programming Language :: Python :: 3",
19 "Programming Language :: Python :: 3",
20 "Programming Language :: Python :: 3 :: Only",
20 "Programming Language :: Python :: 3 :: Only",
21 "Topic :: System :: Shells",
21 "Topic :: System :: Shells",
22 ]
22 ]
23 requires-python = ">=3.10"
23 requires-python = ">=3.10"
24 dependencies = [
24 dependencies = [
25 'colorama; sys_platform == "win32"',
25 'colorama; sys_platform == "win32"',
26 "decorator",
26 "decorator",
27 "exceptiongroup; python_version<'3.11'",
27 "exceptiongroup; python_version<'3.11'",
28 "jedi>=0.16",
28 "jedi>=0.16",
29 "matplotlib-inline",
29 "matplotlib-inline",
30 'pexpect>4.3; sys_platform != "win32" and sys_platform != "emscripten"',
30 'pexpect>4.3; sys_platform != "win32" and sys_platform != "emscripten"',
31 "prompt_toolkit>=3.0.41,<3.1.0",
31 "prompt_toolkit>=3.0.41,<3.1.0",
32 "pygments>=2.4.0",
32 "pygments>=2.4.0",
33 "stack_data",
33 "stack_data",
34 "traitlets>=5.13.0",
34 "traitlets>=5.13.0",
35 "typing_extensions>=4.6; python_version<'3.12'",
35 "typing_extensions>=4.6; python_version<'3.12'",
36 ]
36 ]
37 dynamic = ["authors", "license", "version"]
37 dynamic = ["authors", "license", "version"]
38
38
39 [project.entry-points."pygments.lexers"]
39 [project.entry-points."pygments.lexers"]
40 ipythonconsole = "IPython.lib.lexers:IPythonConsoleLexer"
40 ipythonconsole = "IPython.lib.lexers:IPythonConsoleLexer"
41 ipython = "IPython.lib.lexers:IPythonLexer"
41 ipython = "IPython.lib.lexers:IPythonLexer"
42 ipython3 = "IPython.lib.lexers:IPython3Lexer"
42 ipython3 = "IPython.lib.lexers:IPython3Lexer"
43
43
44 [project.scripts]
44 [project.scripts]
45 ipython = "IPython:start_ipython"
45 ipython = "IPython:start_ipython"
46 ipython3 = "IPython:start_ipython"
46 ipython3 = "IPython:start_ipython"
47
47
48 [project.readme]
48 [project.readme]
49 file = "long_description.rst"
49 file = "long_description.rst"
50 content-type = "text/x-rst"
50 content-type = "text/x-rst"
51
51
52 [project.urls]
52 [project.urls]
53 Homepage = "https://ipython.org"
53 Homepage = "https://ipython.org"
54 Documentation = "https://ipython.readthedocs.io/"
54 Documentation = "https://ipython.readthedocs.io/"
55 Funding = "https://numfocus.org/"
55 Funding = "https://numfocus.org/"
56 Source = "https://github.com/ipython/ipython"
56 Source = "https://github.com/ipython/ipython"
57 Tracker = "https://github.com/ipython/ipython/issues"
57 Tracker = "https://github.com/ipython/ipython/issues"
58
58
59 [project.optional-dependencies]
59 [project.optional-dependencies]
60 black = [
60 black = [
61 "black",
61 "black",
62 ]
62 ]
63 doc = [
63 doc = [
64 "docrepr",
64 "docrepr",
65 "exceptiongroup",
65 "exceptiongroup",
66 "intersphinx_registry",
66 "intersphinx_registry",
67 "ipykernel",
67 "ipykernel",
68 "ipython[test]",
68 "ipython[test]",
69 "matplotlib",
69 "matplotlib",
70 "setuptools>=18.5",
70 "setuptools>=18.5",
71 "sphinx-rtd-theme",
71 "sphinx-rtd-theme",
72 "sphinx>=1.3",
72 "sphinx>=1.3",
73 "sphinxcontrib-jquery",
73 "sphinxcontrib-jquery",
74 "tomli ; python_version<'3.11'",
74 "tomli ; python_version<'3.11'",
75 "typing_extensions",
75 "typing_extensions",
76 ]
76 ]
77 kernel = [
77 kernel = [
78 "ipykernel",
78 "ipykernel",
79 ]
79 ]
80 nbconvert = [
80 nbconvert = [
81 "nbconvert",
81 "nbconvert",
82 ]
82 ]
83 nbformat = [
83 nbformat = [
84 "nbformat",
84 "nbformat",
85 ]
85 ]
86 notebook = [
86 notebook = [
87 "ipywidgets",
87 "ipywidgets",
88 "notebook",
88 "notebook",
89 ]
89 ]
90 parallel = [
90 parallel = [
91 "ipyparallel",
91 "ipyparallel",
92 ]
92 ]
93 qtconsole = [
93 qtconsole = [
94 "qtconsole",
94 "qtconsole",
95 ]
95 ]
96 terminal = []
96 terminal = []
97 test = [
97 test = [
98 "pytest",
98 "pytest",
99 "pytest-asyncio<0.22",
99 "pytest-asyncio<0.22",
100 "testpath",
100 "testpath",
101 "pickleshare",
101 "pickleshare",
102 ]
102 ]
103 test_extra = [
103 test_extra = [
104 "ipython[test]",
104 "ipython[test]",
105 "curio",
105 "curio",
106 "matplotlib!=3.2.0",
106 "matplotlib!=3.2.0",
107 "nbformat",
107 "nbformat",
108 "numpy>=1.23",
108 "numpy>=1.23",
109 "pandas",
109 "pandas",
110 "trio",
110 "trio",
111 ]
111 ]
112 matplotlib = [
112 matplotlib = [
113 "matplotlib"
113 "matplotlib"
114 ]
114 ]
115 all = [
115 all = [
116 "ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,matplotlib]",
116 "ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,matplotlib]",
117 "ipython[test,test_extra]",
117 "ipython[test,test_extra]",
118 ]
118 ]
119
119
120 [tool.mypy]
120 [tool.mypy]
121 python_version = "3.10"
121 python_version = "3.10"
122 ignore_missing_imports = true
122 ignore_missing_imports = true
123 follow_imports = 'silent'
123 follow_imports = 'silent'
124 exclude = [
124 exclude = [
125 'test_\.+\.py',
125 'test_\.+\.py',
126 'IPython.utils.tests.test_wildcard',
126 'IPython.utils.tests.test_wildcard',
127 'testing',
127 'testing',
128 'tests',
128 'tests',
129 'PyColorize.py',
129 'PyColorize.py',
130 '_process_win32_controller.py',
130 '_process_win32_controller.py',
131 'IPython/core/application.py',
131 'IPython/core/application.py',
132 'IPython/core/profileapp.py',
132 'IPython/core/profileapp.py',
133 'IPython/lib/deepreload.py',
133 'IPython/lib/deepreload.py',
134 'IPython/sphinxext/ipython_directive.py',
134 'IPython/sphinxext/ipython_directive.py',
135 'IPython/terminal/ipapp.py',
135 'IPython/terminal/ipapp.py',
136 'IPython/utils/_process_win32.py',
136 'IPython/utils/_process_win32.py',
137 'IPython/utils/path.py',
137 'IPython/utils/path.py',
138 ]
138 ]
139 disallow_untyped_defs = true
140 # ignore_errors = false
141 # ignore_missing_imports = false
142 # disallow_untyped_calls = true
143 disallow_incomplete_defs = true
144 # check_untyped_defs = true
145 # disallow_untyped_decorators = true
146 warn_redundant_casts = true
147
148 [[tool.mypy.overrides]]
149 module = [
150 "IPython.utils.text",
151 ]
152 disallow_untyped_defs = false
153 check_untyped_defs = false
154 disallow_untyped_decorators = false
155
156
157 # gloabl ignore error
158 [[tool.mypy.overrides]]
159 module = [
160 "IPython",
161 "IPython.conftest",
162 "IPython.core.alias",
163 "IPython.core.async_helpers",
164 "IPython.core.autocall",
165 "IPython.core.builtin_trap",
166 "IPython.core.compilerop",
167 "IPython.core.completer",
168 "IPython.core.completerlib",
169 "IPython.core.crashhandler",
170 "IPython.core.debugger",
171 "IPython.core.display",
172 "IPython.core.display_functions",
173 "IPython.core.display_trap",
174 "IPython.core.displayhook",
175 "IPython.core.displaypub",
176 "IPython.core.events",
177 "IPython.core.excolors",
178 "IPython.core.extensions",
179 "IPython.core.formatters",
180 "IPython.core.getipython",
181 "IPython.core.guarded_eval",
182 "IPython.core.history",
183 "IPython.core.historyapp",
184 "IPython.core.hooks",
185 "IPython.core.inputsplitter",
186 "IPython.core.inputtransformer",
187 "IPython.core.inputtransformer2",
188 "IPython.core.interactiveshell",
189 "IPython.core.logger",
190 "IPython.core.macro",
191 "IPython.core.magic",
192 "IPython.core.magic_arguments",
193 "IPython.core.magics.ast_mod",
194 "IPython.core.magics.auto",
195 "IPython.core.magics.basic",
196 "IPython.core.magics.code",
197 "IPython.core.magics.config",
198 "IPython.core.magics.display",
199 "IPython.core.magics.execution",
200 "IPython.core.magics.extension",
201 "IPython.core.magics.history",
202 "IPython.core.magics.logging",
203 "IPython.core.magics.namespace",
204 "IPython.core.magics.osm",
205 "IPython.core.magics.packaging",
206 "IPython.core.magics.pylab",
207 "IPython.core.magics.script",
208 "IPython.core.oinspect",
209 "IPython.core.page",
210 "IPython.core.payload",
211 "IPython.core.payloadpage",
212 "IPython.core.prefilter",
213 "IPython.core.profiledir",
214 "IPython.core.prompts",
215 "IPython.core.pylabtools",
216 "IPython.core.shellapp",
217 "IPython.core.splitinput",
218 "IPython.core.ultratb",
219 "IPython.extensions.autoreload",
220 "IPython.extensions.storemagic",
221 "IPython.external.qt_for_kernel",
222 "IPython.external.qt_loaders",
223 "IPython.lib.backgroundjobs",
224 "IPython.lib.clipboard",
225 "IPython.lib.demo",
226 "IPython.lib.display",
227 "IPython.lib.editorhooks",
228 "IPython.lib.guisupport",
229 "IPython.lib.latextools",
230 "IPython.lib.lexers",
231 "IPython.lib.pretty",
232 "IPython.paths",
233 "IPython.sphinxext.ipython_console_highlighting",
234 "IPython.terminal.debugger",
235 "IPython.terminal.embed",
236 "IPython.terminal.interactiveshell",
237 "IPython.terminal.magics",
238 "IPython.terminal.prompts",
239 "IPython.terminal.pt_inputhooks",
240 "IPython.terminal.pt_inputhooks.asyncio",
241 "IPython.terminal.pt_inputhooks.glut",
242 "IPython.terminal.pt_inputhooks.gtk",
243 "IPython.terminal.pt_inputhooks.gtk3",
244 "IPython.terminal.pt_inputhooks.gtk4",
245 "IPython.terminal.pt_inputhooks.osx",
246 "IPython.terminal.pt_inputhooks.pyglet",
247 "IPython.terminal.pt_inputhooks.qt",
248 "IPython.terminal.pt_inputhooks.tk",
249 "IPython.terminal.pt_inputhooks.wx",
250 "IPython.terminal.ptutils",
251 "IPython.terminal.shortcuts",
252 "IPython.terminal.shortcuts.auto_match",
253 "IPython.terminal.shortcuts.auto_suggest",
254 "IPython.terminal.shortcuts.filters",
255 "IPython.utils._process_cli",
256 "IPython.utils._process_common",
257 "IPython.utils._process_emscripten",
258 "IPython.utils._process_posix",
259 "IPython.utils.capture",
260 "IPython.utils.coloransi",
261 "IPython.utils.contexts",
262 "IPython.utils.data",
263 "IPython.utils.decorators",
264 "IPython.utils.dir2",
265 "IPython.utils.encoding",
266 "IPython.utils.frame",
267 "IPython.utils.generics",
268 "IPython.utils.importstring",
269 "IPython.utils.io",
270 "IPython.utils.ipstruct",
271 "IPython.utils.module_paths",
272 "IPython.utils.openpy",
273 "IPython.utils.process",
274 "IPython.utils.py3compat",
275 "IPython.utils.sentinel",
276 "IPython.utils.shimmodule",
277 "IPython.utils.strdispatch",
278 "IPython.utils.sysinfo",
279 "IPython.utils.syspathcontext",
280 "IPython.utils.tempdir",
281 "IPython.utils.terminal",
282 "IPython.utils.timing",
283 "IPython.utils.tokenutil",
284 "IPython.utils.tz",
285 "IPython.utils.ulinecache",
286 "IPython.utils.version",
287 "IPython.utils.wildcard",
288
289 ]
290 disallow_untyped_defs = false
291 ignore_errors = true
292 ignore_missing_imports = true
293 disallow_untyped_calls = false
294 disallow_incomplete_defs = false
295 check_untyped_defs = false
296 disallow_untyped_decorators = false
139
297
140 [tool.pytest.ini_options]
298 [tool.pytest.ini_options]
141 addopts = [
299 addopts = [
142 "--durations=10",
300 "--durations=10",
143 "-pIPython.testing.plugin.pytest_ipdoctest",
301 "-pIPython.testing.plugin.pytest_ipdoctest",
144 "--ipdoctest-modules",
302 "--ipdoctest-modules",
145 "--ignore=docs",
303 "--ignore=docs",
146 "--ignore=examples",
304 "--ignore=examples",
147 "--ignore=htmlcov",
305 "--ignore=htmlcov",
148 "--ignore=ipython_kernel",
306 "--ignore=ipython_kernel",
149 "--ignore=ipython_parallel",
307 "--ignore=ipython_parallel",
150 "--ignore=results",
308 "--ignore=results",
151 "--ignore=tmp",
309 "--ignore=tmp",
152 "--ignore=tools",
310 "--ignore=tools",
153 "--ignore=traitlets",
311 "--ignore=traitlets",
154 "--ignore=IPython/core/tests/daft_extension",
312 "--ignore=IPython/core/tests/daft_extension",
155 "--ignore=IPython/sphinxext",
313 "--ignore=IPython/sphinxext",
156 "--ignore=IPython/terminal/pt_inputhooks",
314 "--ignore=IPython/terminal/pt_inputhooks",
157 "--ignore=IPython/__main__.py",
315 "--ignore=IPython/__main__.py",
158 "--ignore=IPython/external/qt_for_kernel.py",
316 "--ignore=IPython/external/qt_for_kernel.py",
159 "--ignore=IPython/html/widgets/widget_link.py",
317 "--ignore=IPython/html/widgets/widget_link.py",
160 "--ignore=IPython/html/widgets/widget_output.py",
318 "--ignore=IPython/html/widgets/widget_output.py",
161 "--ignore=IPython/terminal/console.py",
319 "--ignore=IPython/terminal/console.py",
162 "--ignore=IPython/utils/_process_cli.py",
320 "--ignore=IPython/utils/_process_cli.py",
163 "--ignore=IPython/utils/_process_posix.py",
321 "--ignore=IPython/utils/_process_posix.py",
164 "--ignore=IPython/utils/_process_win32.py",
322 "--ignore=IPython/utils/_process_win32.py",
165 "--ignore=IPython/utils/_process_win32_controller.py",
323 "--ignore=IPython/utils/_process_win32_controller.py",
166 "--ignore=IPython/utils/daemonize.py",
324 "--ignore=IPython/utils/daemonize.py",
167 "--ignore=IPython/utils/eventful.py",
325 "--ignore=IPython/utils/eventful.py",
168 "--ignore=IPython/kernel",
326 "--ignore=IPython/kernel",
169 "--ignore=IPython/consoleapp.py",
327 "--ignore=IPython/consoleapp.py",
170 "--ignore=IPython/core/inputsplitter.py",
328 "--ignore=IPython/core/inputsplitter.py",
171 "--ignore=IPython/lib/kernel.py",
329 "--ignore=IPython/lib/kernel.py",
172 "--ignore=IPython/utils/jsonutil.py",
330 "--ignore=IPython/utils/jsonutil.py",
173 "--ignore=IPython/utils/localinterfaces.py",
331 "--ignore=IPython/utils/localinterfaces.py",
174 "--ignore=IPython/utils/log.py",
332 "--ignore=IPython/utils/log.py",
175 "--ignore=IPython/utils/signatures.py",
333 "--ignore=IPython/utils/signatures.py",
176 "--ignore=IPython/utils/traitlets.py",
334 "--ignore=IPython/utils/traitlets.py",
177 "--ignore=IPython/utils/version.py"
335 "--ignore=IPython/utils/version.py"
178 ]
336 ]
179 doctest_optionflags = [
337 doctest_optionflags = [
180 "NORMALIZE_WHITESPACE",
338 "NORMALIZE_WHITESPACE",
181 "ELLIPSIS"
339 "ELLIPSIS"
182 ]
340 ]
183 ipdoctest_optionflags = [
341 ipdoctest_optionflags = [
184 "NORMALIZE_WHITESPACE",
342 "NORMALIZE_WHITESPACE",
185 "ELLIPSIS"
343 "ELLIPSIS"
186 ]
344 ]
187 asyncio_mode = "strict"
345 asyncio_mode = "strict"
188
346
189 [tool.pyright]
347 [tool.pyright]
190 pythonPlatform="All"
348 pythonPlatform="All"
191
349
192 [tool.setuptools]
350 [tool.setuptools]
193 zip-safe = false
351 zip-safe = false
194 platforms = ["Linux", "Mac OSX", "Windows"]
352 platforms = ["Linux", "Mac OSX", "Windows"]
195 license-files = ["LICENSE"]
353 license-files = ["LICENSE"]
196 include-package-data = false
354 include-package-data = false
197
355
198 [tool.setuptools.packages.find]
356 [tool.setuptools.packages.find]
199 exclude = ["setupext"]
357 exclude = ["setupext"]
200 namespaces = false
358 namespaces = false
201
359
202 [tool.setuptools.package-data]
360 [tool.setuptools.package-data]
203 "IPython" = ["py.typed"]
361 "IPython" = ["py.typed"]
204 "IPython.core" = ["profile/README*"]
362 "IPython.core" = ["profile/README*"]
205 "IPython.core.tests" = ["*.png", "*.jpg", "daft_extension/*.py"]
363 "IPython.core.tests" = ["*.png", "*.jpg", "daft_extension/*.py"]
206 "IPython.lib.tests" = ["*.wav"]
364 "IPython.lib.tests" = ["*.wav"]
207 "IPython.testing.plugin" = ["*.txt"]
365 "IPython.testing.plugin" = ["*.txt"]
208
366
209 [tool.setuptools.dynamic]
367 [tool.setuptools.dynamic]
210 version = {attr = "IPython.core.release.__version__"}
368 version = {attr = "IPython.core.release.__version__"}
General Comments 0
You need to be logged in to leave comments. Login now