##// END OF EJS Templates
Fix docstring leading space....
Matthias Bussonnier -
Show More
@@ -1,140 +1,142 b''
1 #-----------------------------------------------------------------------------
1 #-----------------------------------------------------------------------------
2 # Copyright (C) 2010-2011, IPython Development Team.
2 # Copyright (C) 2010-2011, IPython Development Team.
3 #
3 #
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5 #
5 #
6 # The full license is in the file COPYING.txt, distributed with this software.
6 # The full license is in the file COPYING.txt, distributed with this software.
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 import argparse
9 import argparse
10 import sys
10 import sys
11
11
12 from IPython.core.magic_arguments import (argument, argument_group, kwds,
12 from IPython.core.magic_arguments import (argument, argument_group, kwds,
13 magic_arguments, parse_argstring, real_name)
13 magic_arguments, parse_argstring, real_name)
14
14
15 LEADING_SPACE = "" if sys.version_info > (3, 13) else " "
16
15
17
16 @magic_arguments()
18 @magic_arguments()
17 @argument('-f', '--foo', help="an argument")
19 @argument('-f', '--foo', help="an argument")
18 def magic_foo1(self, args):
20 def magic_foo1(self, args):
19 """ A docstring.
21 """ A docstring.
20 """
22 """
21 return parse_argstring(magic_foo1, args)
23 return parse_argstring(magic_foo1, args)
22
24
23
25
24 @magic_arguments()
26 @magic_arguments()
25 def magic_foo2(self, args):
27 def magic_foo2(self, args):
26 """ A docstring.
28 """ A docstring.
27 """
29 """
28 return parse_argstring(magic_foo2, args)
30 return parse_argstring(magic_foo2, args)
29
31
30
32
31 @magic_arguments()
33 @magic_arguments()
32 @argument('-f', '--foo', help="an argument")
34 @argument('-f', '--foo', help="an argument")
33 @argument_group('Group')
35 @argument_group('Group')
34 @argument('-b', '--bar', help="a grouped argument")
36 @argument('-b', '--bar', help="a grouped argument")
35 @argument_group('Second Group')
37 @argument_group('Second Group')
36 @argument('-z', '--baz', help="another grouped argument")
38 @argument('-z', '--baz', help="another grouped argument")
37 def magic_foo3(self, args):
39 def magic_foo3(self, args):
38 """ A docstring.
40 """ A docstring.
39 """
41 """
40 return parse_argstring(magic_foo3, args)
42 return parse_argstring(magic_foo3, args)
41
43
42
44
43 @magic_arguments()
45 @magic_arguments()
44 @kwds(argument_default=argparse.SUPPRESS)
46 @kwds(argument_default=argparse.SUPPRESS)
45 @argument('-f', '--foo', help="an argument")
47 @argument('-f', '--foo', help="an argument")
46 def magic_foo4(self, args):
48 def magic_foo4(self, args):
47 """ A docstring.
49 """ A docstring.
48 """
50 """
49 return parse_argstring(magic_foo4, args)
51 return parse_argstring(magic_foo4, args)
50
52
51
53
52 @magic_arguments('frobnicate')
54 @magic_arguments('frobnicate')
53 @argument('-f', '--foo', help="an argument")
55 @argument('-f', '--foo', help="an argument")
54 def magic_foo5(self, args):
56 def magic_foo5(self, args):
55 """ A docstring.
57 """ A docstring.
56 """
58 """
57 return parse_argstring(magic_foo5, args)
59 return parse_argstring(magic_foo5, args)
58
60
59
61
60 @magic_arguments()
62 @magic_arguments()
61 @argument('-f', '--foo', help="an argument")
63 @argument('-f', '--foo', help="an argument")
62 def magic_magic_foo(self, args):
64 def magic_magic_foo(self, args):
63 """ A docstring.
65 """ A docstring.
64 """
66 """
65 return parse_argstring(magic_magic_foo, args)
67 return parse_argstring(magic_magic_foo, args)
66
68
67
69
68 @magic_arguments()
70 @magic_arguments()
69 @argument('-f', '--foo', help="an argument")
71 @argument('-f', '--foo', help="an argument")
70 def foo(self, args):
72 def foo(self, args):
71 """ A docstring.
73 """ A docstring.
72 """
74 """
73 return parse_argstring(foo, args)
75 return parse_argstring(foo, args)
74
76
75
77
76 def test_magic_arguments():
78 def test_magic_arguments():
77 # β€œoptional arguments” was replaced with β€œoptions” in argparse help
79 # β€œoptional arguments” was replaced with β€œoptions” in argparse help
78 # https://docs.python.org/3/whatsnew/3.10.html#argparse
80 # https://docs.python.org/3/whatsnew/3.10.html#argparse
79 # https://bugs.python.org/issue9694
81 # https://bugs.python.org/issue9694
80 options = "optional arguments" if sys.version_info < (3, 10) else "options"
82 options = "optional arguments" if sys.version_info < (3, 10) else "options"
81
83
82 assert (
84 assert (
83 magic_foo1.__doc__
85 magic_foo1.__doc__
84 == f"::\n\n %foo1 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
86 == f"::\n\n %foo1 [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
85 )
87 )
86 assert getattr(magic_foo1, "argcmd_name", None) == None
88 assert getattr(magic_foo1, "argcmd_name", None) == None
87 assert real_name(magic_foo1) == "foo1"
89 assert real_name(magic_foo1) == "foo1"
88 assert magic_foo1(None, "") == argparse.Namespace(foo=None)
90 assert magic_foo1(None, "") == argparse.Namespace(foo=None)
89 assert hasattr(magic_foo1, "has_arguments")
91 assert hasattr(magic_foo1, "has_arguments")
90
92
91 assert magic_foo2.__doc__ == "::\n\n %foo2\n\n A docstring.\n"
93 assert magic_foo2.__doc__ == f"::\n\n %foo2\n\n{LEADING_SPACE}A docstring.\n"
92 assert getattr(magic_foo2, "argcmd_name", None) == None
94 assert getattr(magic_foo2, "argcmd_name", None) == None
93 assert real_name(magic_foo2) == "foo2"
95 assert real_name(magic_foo2) == "foo2"
94 assert magic_foo2(None, "") == argparse.Namespace()
96 assert magic_foo2(None, "") == argparse.Namespace()
95 assert hasattr(magic_foo2, "has_arguments")
97 assert hasattr(magic_foo2, "has_arguments")
96
98
97 assert (
99 assert (
98 magic_foo3.__doc__
100 magic_foo3.__doc__
99 == f"::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n"
101 == f"::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n"
100 )
102 )
101 assert getattr(magic_foo3, "argcmd_name", None) == None
103 assert getattr(magic_foo3, "argcmd_name", None) == None
102 assert real_name(magic_foo3) == "foo3"
104 assert real_name(magic_foo3) == "foo3"
103 assert magic_foo3(None, "") == argparse.Namespace(bar=None, baz=None, foo=None)
105 assert magic_foo3(None, "") == argparse.Namespace(bar=None, baz=None, foo=None)
104 assert hasattr(magic_foo3, "has_arguments")
106 assert hasattr(magic_foo3, "has_arguments")
105
107
106 assert (
108 assert (
107 magic_foo4.__doc__
109 magic_foo4.__doc__
108 == f"::\n\n %foo4 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
110 == f"::\n\n %foo4 [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
109 )
111 )
110 assert getattr(magic_foo4, "argcmd_name", None) == None
112 assert getattr(magic_foo4, "argcmd_name", None) == None
111 assert real_name(magic_foo4) == "foo4"
113 assert real_name(magic_foo4) == "foo4"
112 assert magic_foo4(None, "") == argparse.Namespace()
114 assert magic_foo4(None, "") == argparse.Namespace()
113 assert hasattr(magic_foo4, "has_arguments")
115 assert hasattr(magic_foo4, "has_arguments")
114
116
115 assert (
117 assert (
116 magic_foo5.__doc__
118 magic_foo5.__doc__
117 == f"::\n\n %frobnicate [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
119 == f"::\n\n %frobnicate [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
118 )
120 )
119 assert getattr(magic_foo5, "argcmd_name", None) == "frobnicate"
121 assert getattr(magic_foo5, "argcmd_name", None) == "frobnicate"
120 assert real_name(magic_foo5) == "frobnicate"
122 assert real_name(magic_foo5) == "frobnicate"
121 assert magic_foo5(None, "") == argparse.Namespace(foo=None)
123 assert magic_foo5(None, "") == argparse.Namespace(foo=None)
122 assert hasattr(magic_foo5, "has_arguments")
124 assert hasattr(magic_foo5, "has_arguments")
123
125
124 assert (
126 assert (
125 magic_magic_foo.__doc__
127 magic_magic_foo.__doc__
126 == f"::\n\n %magic_foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
128 == f"::\n\n %magic_foo [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
127 )
129 )
128 assert getattr(magic_magic_foo, "argcmd_name", None) == None
130 assert getattr(magic_magic_foo, "argcmd_name", None) == None
129 assert real_name(magic_magic_foo) == "magic_foo"
131 assert real_name(magic_magic_foo) == "magic_foo"
130 assert magic_magic_foo(None, "") == argparse.Namespace(foo=None)
132 assert magic_magic_foo(None, "") == argparse.Namespace(foo=None)
131 assert hasattr(magic_magic_foo, "has_arguments")
133 assert hasattr(magic_magic_foo, "has_arguments")
132
134
133 assert (
135 assert (
134 foo.__doc__
136 foo.__doc__
135 == f"::\n\n %foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
137 == f"::\n\n %foo [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
136 )
138 )
137 assert getattr(foo, "argcmd_name", None) == None
139 assert getattr(foo, "argcmd_name", None) == None
138 assert real_name(foo) == "foo"
140 assert real_name(foo) == "foo"
139 assert foo(None, "") == argparse.Namespace(foo=None)
141 assert foo(None, "") == argparse.Namespace(foo=None)
140 assert hasattr(foo, "has_arguments")
142 assert hasattr(foo, "has_arguments")
@@ -1,799 +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, 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: str) -> str:
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(
655 def compute_item_matrix(
656 items, row_first: bool = False, empty=None, *, separator_size=2, displaywidth=80
656 items, row_first: bool = False, empty=None, *, separator_size=2, displaywidth=80
657 ) -> Tuple[List[List[int]], Dict[str, int]]:
657 ) -> Tuple[List[List[int]], Dict[str, int]]:
658 """Returns a nested list, and info to columnize items
658 """Returns a nested list, and info to columnize items
659
659
660 Parameters
660 Parameters
661 ----------
661 ----------
662 items
662 items
663 list of strings to columize
663 list of strings to columize
664 row_first : (default False)
664 row_first : (default False)
665 Whether to compute columns for a row-first matrix instead of
665 Whether to compute columns for a row-first matrix instead of
666 column-first (default).
666 column-first (default).
667 empty : (default None)
667 empty : (default None)
668 default value to fill list if needed
668 default value to fill list if needed
669 separator_size : int (default=2)
669 separator_size : int (default=2)
670 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.
671 displaywidth : int (default=80)
671 displaywidth : int (default=80)
672 The width of the area onto which the columns should enter
672 The width of the area onto which the columns should enter
673
673
674 Returns
674 Returns
675 -------
675 -------
676 strings_matrix
676 strings_matrix
677 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
678 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
679 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
680 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`.
681 dict_info
681 dict_info
682 some info to make columnize easier:
682 some info to make columnize easier:
683
683
684 num_columns
684 num_columns
685 number of columns
685 number of columns
686 max_rows
686 max_rows
687 maximum number of rows (final number may be less)
687 maximum number of rows (final number may be less)
688 column_widths
688 column_widths
689 list of with of each columns
689 list of with of each columns
690 optimal_separator_width
690 optimal_separator_width
691 best separator width between columns
691 best separator width between columns
692
692
693 Examples
693 Examples
694 --------
694 --------
695 ::
695 ::
696
696
697 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']
698 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
698 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
699 In [3]: list
699 In [3]: list
700 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]]
701 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}
702 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()))
703 Out[5]: True
703 Out[5]: True
704 """
704 """
705 warnings.warn(
705 warnings.warn(
706 "`compute_item_matrix` is Pending Deprecation since IPython 8.17."
706 "`compute_item_matrix` is Pending Deprecation since IPython 8.17."
707 "It is considered fro removal in in future version. "
707 "It is considered fro removal in in future version. "
708 "Please open an issue if you believe it should be kept.",
708 "Please open an issue if you believe it should be kept.",
709 stacklevel=2,
709 stacklevel=2,
710 category=PendingDeprecationWarning,
710 category=PendingDeprecationWarning,
711 )
711 )
712 info = _find_optimal(
712 info = _find_optimal(
713 list(map(len, items)),
713 list(map(len, items)),
714 row_first,
714 row_first,
715 separator_size=separator_size,
715 separator_size=separator_size,
716 displaywidth=displaywidth,
716 displaywidth=displaywidth,
717 )
717 )
718 nrow, ncol = info["max_rows"], info["num_columns"]
718 nrow, ncol = info["max_rows"], info["num_columns"]
719 if row_first:
719 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)
720 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
721 else:
721 else:
722 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)
723
723
724
724
725 def columnize(items, row_first=False, separator=" ", displaywidth=80, spread=False):
725 def columnize(items, row_first=False, separator=" ", displaywidth=80, spread=False):
726 """Transform a list of strings into a single string with columns.
726 """Transform a list of strings into a single string with columns.
727
727
728 Parameters
728 Parameters
729 ----------
729 ----------
730 items : sequence of strings
730 items : sequence of strings
731 The strings to process.
731 The strings to process.
732 row_first : (default False)
732 row_first : (default False)
733 Whether to compute columns for a row-first matrix instead of
733 Whether to compute columns for a row-first matrix instead of
734 column-first (default).
734 column-first (default).
735 separator : str, optional [default is two spaces]
735 separator : str, optional [default is two spaces]
736 The string that separates columns.
736 The string that separates columns.
737 displaywidth : int, optional [default is 80]
737 displaywidth : int, optional [default is 80]
738 Width of the display in number of characters.
738 Width of the display in number of characters.
739
739
740 Returns
740 Returns
741 -------
741 -------
742 The formatted string.
742 The formatted string.
743 """
743 """
744 warnings.warn(
744 warnings.warn(
745 "`columnize` is Pending Deprecation since IPython 8.17."
745 "`columnize` is Pending Deprecation since IPython 8.17."
746 "It is considered fro removal in in future version. "
746 "It is considered fro removal in in future version. "
747 "Please open an issue if you believe it should be kept.",
747 "Please open an issue if you believe it should be kept.",
748 stacklevel=2,
748 stacklevel=2,
749 category=PendingDeprecationWarning,
749 category=PendingDeprecationWarning,
750 )
750 )
751 if not items:
751 if not items:
752 return "\n"
752 return "\n"
753 matrix: List[List[int]]
753 matrix: List[List[int]]
754 matrix, info = compute_item_matrix(
754 matrix, info = compute_item_matrix(
755 items,
755 items,
756 row_first=row_first,
756 row_first=row_first,
757 separator_size=len(separator),
757 separator_size=len(separator),
758 displaywidth=displaywidth,
758 displaywidth=displaywidth,
759 )
759 )
760 if spread:
760 if spread:
761 separator = separator.ljust(int(info["optimal_separator_width"]))
761 separator = separator.ljust(int(info["optimal_separator_width"]))
762 fmatrix: List[filter[int]] = [filter(None, x) for x in matrix]
762 fmatrix: List[filter[int]] = [filter(None, x) for x in matrix]
763 sjoin = lambda x: separator.join(
763 sjoin = lambda x: separator.join(
764 [y.ljust(w, " ") for y, w in zip(x, info["column_widths"])]
764 [y.ljust(w, " ") for y, w in zip(x, info["column_widths"])]
765 )
765 )
766 return "\n".join(map(sjoin, fmatrix)) + "\n"
766 return "\n".join(map(sjoin, fmatrix)) + "\n"
767
767
768
768
769 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=""):
770 """
770 """
771 Return a string with a natural enumeration of items
771 Return a string with a natural enumeration of items
772
772
773 >>> get_text_list(['a', 'b', 'c', 'd'])
773 >>> get_text_list(['a', 'b', 'c', 'd'])
774 'a, b, c and d'
774 'a, b, c and d'
775 >>> get_text_list(['a', 'b', 'c'], ' or ')
775 >>> get_text_list(['a', 'b', 'c'], ' or ')
776 'a, b or c'
776 'a, b or c'
777 >>> get_text_list(['a', 'b', 'c'], ', ')
777 >>> get_text_list(['a', 'b', 'c'], ', ')
778 'a, b, c'
778 'a, b, c'
779 >>> get_text_list(['a', 'b'], ' or ')
779 >>> get_text_list(['a', 'b'], ' or ')
780 'a or b'
780 'a or b'
781 >>> get_text_list(['a'])
781 >>> get_text_list(['a'])
782 'a'
782 'a'
783 >>> get_text_list([])
783 >>> get_text_list([])
784 ''
784 ''
785 >>> get_text_list(['a', 'b'], wrap_item_with="`")
785 >>> get_text_list(['a', 'b'], wrap_item_with="`")
786 '`a` and `b`'
786 '`a` and `b`'
787 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
787 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
788 'a + b + c = d'
788 'a + b + c = d'
789 """
789 """
790 if len(list_) == 0:
790 if len(list_) == 0:
791 return ''
791 return ''
792 if wrap_item_with:
792 if wrap_item_with:
793 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
794 item in list_]
794 item in list_]
795 if len(list_) == 1:
795 if len(list_) == 1:
796 return list_[0]
796 return list_[0]
797 return '%s%s%s' % (
797 return '%s%s%s' % (
798 sep.join(i for i in list_[:-1]),
798 sep.join(i for i in list_[:-1]),
799 last_sep, list_[-1])
799 last_sep, list_[-1])
General Comments 0
You need to be logged in to leave comments. Login now