##// END OF EJS Templates
Merge pull request #4758 from takluyver/testing-py34...
Thomas Kluyver -
r14094:ae1f2bef merge
parent child Browse files
Show More
@@ -1,58 +1,59 b''
1 1 #-----------------------------------------------------------------------------
2 2 # Copyright (c) 2013, the IPython Development Team.
3 3 #
4 4 # Distributed under the terms of the Modified BSD License.
5 5 #
6 6 # The full license is in the file COPYING.txt, distributed with this software.
7 7 #-----------------------------------------------------------------------------
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Imports
11 11 #-----------------------------------------------------------------------------
12 12
13 13 from ..citation import citation2latex
14 from nose.tools import assert_equal
14 15
15 16 #-----------------------------------------------------------------------------
16 17 # Tests
17 18 #-----------------------------------------------------------------------------
18 19
19 20 test_md = """
20 21 # My Heading
21 22
22 23 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ac magna non augue
23 24 porttitor scelerisque ac id diam <cite data-cite="granger">Granger</cite>. Mauris elit
24 25 velit, lobortis sed interdum at, vestibulum vitae libero <strong data-cite="fperez">Perez</strong>.
25 26 Lorem ipsum dolor sit amet, consectetur adipiscing elit
26 27 <em data-cite="takluyver">Thomas</em>. Quisque iaculis ligula ut ipsum mattis viverra.
27 28
28 29 <p>Here is a plain paragraph that should be unaffected.</p>
29 30
30 31 * One <cite data-cite="jdfreder">Jonathan</cite>.
31 32 * Two <cite data-cite="carreau">Matthias</cite>.
32 33 * Three <cite data-cite="ivanov">Paul</cite>.
33 34 """
34 35
35 36 test_md_parsed = """
36 37 # My Heading
37 38
38 39 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ac magna non augue
39 40 porttitor scelerisque ac id diam \cite{granger}. Mauris elit
40 41 velit, lobortis sed interdum at, vestibulum vitae libero \cite{fperez}.
41 42 Lorem ipsum dolor sit amet, consectetur adipiscing elit
42 43 \cite{takluyver}. Quisque iaculis ligula ut ipsum mattis viverra.
43 44
44 45 <p>Here is a plain paragraph that should be unaffected.</p>
45 46
46 47 * One \cite{jdfreder}.
47 48 * Two \cite{carreau}.
48 49 * Three \cite{ivanov}.
49 50 """
50 51
51 52 def test_citation2latex():
52 53 """Are citations parsed properly?"""
53 54 try:
54 import lxml
55 from lxml import html #analysis:ignore
55 56 except ImportError:
56 assert test_md == citation2latex(test_md)
57 assert_equal(test_md, citation2latex(test_md))
57 58 else:
58 assert test_md_parsed == citation2latex(test_md)
59 assert_equal(test_md_parsed, citation2latex(test_md))
@@ -1,171 +1,177 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.utils.text"""
3 3 from __future__ import print_function
4 4
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (C) 2011 The IPython Development Team
7 7 #
8 8 # Distributed under the terms of the BSD License. The full license is in
9 9 # the file COPYING, distributed as part of this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
16 16 import os
17 17 import math
18 18 import random
19 import sys
19 20
20 21 import nose.tools as nt
21 22
22 23 from IPython.utils import text
23 24
24 25 #-----------------------------------------------------------------------------
25 26 # Globals
26 27 #-----------------------------------------------------------------------------
27 28
28 29 def test_columnize():
29 30 """Basic columnize tests."""
30 31 size = 5
31 32 items = [l*size for l in 'abc']
32 33 out = text.columnize(items, displaywidth=80)
33 34 nt.assert_equal(out, 'aaaaa bbbbb ccccc\n')
34 35 out = text.columnize(items, displaywidth=12)
35 36 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb\n')
36 37 out = text.columnize(items, displaywidth=10)
37 38 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\n')
38 39
39 40 def test_columnize_random():
40 41 """Test with random input to hopfully catch edge case """
41 42 for nitems in [random.randint(2,70) for i in range(2,20)]:
42 43 displaywidth = random.randint(20,200)
43 44 rand_len = [random.randint(2,displaywidth) for i in range(nitems)]
44 45 items = ['x'*l for l in rand_len]
45 46 out = text.columnize(items, displaywidth=displaywidth)
46 47 longer_line = max([len(x) for x in out.split('\n')])
47 48 longer_element = max(rand_len)
48 49 if longer_line > displaywidth:
49 50 print("Columnize displayed something lager than displaywidth : %s " % longer_line)
50 51 print("longer element : %s " % longer_element)
51 52 print("displaywidth : %s " % displaywidth)
52 53 print("number of element : %s " % nitems)
53 54 print("size of each element :\n %s" % rand_len)
54 55 assert False
55 56
56 57 def test_columnize_medium():
57 58 """Test with inputs than shouldn't be wider tahn 80 """
58 59 size = 40
59 60 items = [l*size for l in 'abc']
60 61 out = text.columnize(items, displaywidth=80)
61 62 nt.assert_equal(out, '\n'.join(items+['']))
62 63
63 64 def test_columnize_long():
64 65 """Test columnize with inputs longer than the display window"""
65 66 size = 11
66 67 items = [l*size for l in 'abc']
67 68 out = text.columnize(items, displaywidth=size-1)
68 69 nt.assert_equal(out, '\n'.join(items+['']))
69 70
70 71 def eval_formatter_check(f):
71 72 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©")
72 73 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
73 74 nt.assert_equal(s, "12 3 hello")
74 75 s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns)
75 76 nt.assert_equal(s, "12 6 4 3 2 2 1")
76 77 s = f.format('{[n//i for i in range(1,8)]}', **ns)
77 78 nt.assert_equal(s, "[12, 6, 4, 3, 2, 2, 1]")
78 79 s = f.format("{stuff!s}", **ns)
79 80 nt.assert_equal(s, ns['stuff'])
80 81 s = f.format("{stuff!r}", **ns)
81 82 nt.assert_equal(s, repr(ns['stuff']))
82 83
83 84 # Check with unicode:
84 85 s = f.format("{u}", **ns)
85 86 nt.assert_equal(s, ns['u'])
86 87 # This decodes in a platform dependent manner, but it shouldn't error out
87 88 s = f.format("{b}", **ns)
88 89
89 90 nt.assert_raises(NameError, f.format, '{dne}', **ns)
90 91
91 92 def eval_formatter_slicing_check(f):
92 93 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
93 94 s = f.format(" {stuff.split()[:]} ", **ns)
94 95 nt.assert_equal(s, " ['hello', 'there'] ")
95 96 s = f.format(" {stuff.split()[::-1]} ", **ns)
96 97 nt.assert_equal(s, " ['there', 'hello'] ")
97 98 s = f.format("{stuff[::2]}", **ns)
98 99 nt.assert_equal(s, ns['stuff'][::2])
99 100
100 101 nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
101 102
102 103 def eval_formatter_no_slicing_check(f):
103 104 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
104 105
105 106 s = f.format('{n:x} {pi**2:+f}', **ns)
106 107 nt.assert_equal(s, "c +9.869604")
107 108
108 109 s = f.format('{stuff[slice(1,4)]}', **ns)
109 110 nt.assert_equal(s, 'ell')
110 111
111 nt.assert_raises(SyntaxError, f.format, "{a[:]}")
112 if sys.version_info >= (3, 4):
113 # String formatting has changed in Python 3.4, so this now works.
114 s = f.format("{a[:]}", a=[1, 2])
115 nt.assert_equal(s, "[1, 2]")
116 else:
117 nt.assert_raises(SyntaxError, f.format, "{a[:]}")
112 118
113 119 def test_eval_formatter():
114 120 f = text.EvalFormatter()
115 121 eval_formatter_check(f)
116 122 eval_formatter_no_slicing_check(f)
117 123
118 124 def test_full_eval_formatter():
119 125 f = text.FullEvalFormatter()
120 126 eval_formatter_check(f)
121 127 eval_formatter_slicing_check(f)
122 128
123 129 def test_dollar_formatter():
124 130 f = text.DollarFormatter()
125 131 eval_formatter_check(f)
126 132 eval_formatter_slicing_check(f)
127 133
128 134 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
129 135 s = f.format("$n", **ns)
130 136 nt.assert_equal(s, "12")
131 137 s = f.format("$n.real", **ns)
132 138 nt.assert_equal(s, "12")
133 139 s = f.format("$n/{stuff[:5]}", **ns)
134 140 nt.assert_equal(s, "12/hello")
135 141 s = f.format("$n $$HOME", **ns)
136 142 nt.assert_equal(s, "12 $HOME")
137 143 s = f.format("${foo}", foo="HOME")
138 144 nt.assert_equal(s, "$HOME")
139 145
140 146
141 147 def test_long_substr():
142 148 data = ['hi']
143 149 nt.assert_equal(text.long_substr(data), 'hi')
144 150
145 151
146 152 def test_long_substr2():
147 153 data = ['abc', 'abd', 'abf', 'ab']
148 154 nt.assert_equal(text.long_substr(data), 'ab')
149 155
150 156 def test_long_substr_empty():
151 157 data = []
152 158 nt.assert_equal(text.long_substr(data), '')
153 159
154 160 def test_strip_email():
155 161 src = """\
156 162 >> >>> def f(x):
157 163 >> ... return x+1
158 164 >> ...
159 165 >> >>> zz = f(2.5)"""
160 166 cln = """\
161 167 >>> def f(x):
162 168 ... return x+1
163 169 ...
164 170 >>> zz = f(2.5)"""
165 171 nt.assert_equal(text.strip_email_quotes(src), cln)
166 172
167 173
168 174 def test_strip_email2():
169 175 src = '> > > list()'
170 176 cln = 'list()'
171 177 nt.assert_equal(text.strip_email_quotes(src), cln)
@@ -1,770 +1,773 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for working with strings and text.
4 4
5 5 Inheritance diagram:
6 6
7 7 .. inheritance-diagram:: IPython.utils.text
8 8 :parts: 3
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 import os
23 23 import re
24 24 import sys
25 25 import textwrap
26 26 from string import Formatter
27 27
28 28 from IPython.external.path import path
29 29 from IPython.testing.skipdoctest import skip_doctest_py3, skip_doctest
30 30 from IPython.utils import py3compat
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Declarations
34 34 #-----------------------------------------------------------------------------
35 35
36 36 # datetime.strftime date format for ipython
37 37 if sys.platform == 'win32':
38 38 date_format = "%B %d, %Y"
39 39 else:
40 40 date_format = "%B %-d, %Y"
41 41
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Code
45 45 #-----------------------------------------------------------------------------
46 46
47 47 class LSString(str):
48 48 """String derivative with a special access attributes.
49 49
50 50 These are normal strings, but with the special attributes:
51 51
52 52 .l (or .list) : value as list (split on newlines).
53 53 .n (or .nlstr): original value (the string itself).
54 54 .s (or .spstr): value as whitespace-separated string.
55 55 .p (or .paths): list of path objects
56 56
57 57 Any values which require transformations are computed only once and
58 58 cached.
59 59
60 60 Such strings are very useful to efficiently interact with the shell, which
61 61 typically only understands whitespace-separated options for commands."""
62 62
63 63 def get_list(self):
64 64 try:
65 65 return self.__list
66 66 except AttributeError:
67 67 self.__list = self.split('\n')
68 68 return self.__list
69 69
70 70 l = list = property(get_list)
71 71
72 72 def get_spstr(self):
73 73 try:
74 74 return self.__spstr
75 75 except AttributeError:
76 76 self.__spstr = self.replace('\n',' ')
77 77 return self.__spstr
78 78
79 79 s = spstr = property(get_spstr)
80 80
81 81 def get_nlstr(self):
82 82 return self
83 83
84 84 n = nlstr = property(get_nlstr)
85 85
86 86 def get_paths(self):
87 87 try:
88 88 return self.__paths
89 89 except AttributeError:
90 90 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
91 91 return self.__paths
92 92
93 93 p = paths = property(get_paths)
94 94
95 95 # FIXME: We need to reimplement type specific displayhook and then add this
96 96 # back as a custom printer. This should also be moved outside utils into the
97 97 # core.
98 98
99 99 # def print_lsstring(arg):
100 100 # """ Prettier (non-repr-like) and more informative printer for LSString """
101 101 # print "LSString (.p, .n, .l, .s available). Value:"
102 102 # print arg
103 103 #
104 104 #
105 105 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
106 106
107 107
108 108 class SList(list):
109 109 """List derivative with a special access attributes.
110 110
111 111 These are normal lists, but with the special attributes:
112 112
113 113 * .l (or .list) : value as list (the list itself).
114 114 * .n (or .nlstr): value as a string, joined on newlines.
115 115 * .s (or .spstr): value as a string, joined on spaces.
116 116 * .p (or .paths): list of path objects
117 117
118 118 Any values which require transformations are computed only once and
119 119 cached."""
120 120
121 121 def get_list(self):
122 122 return self
123 123
124 124 l = list = property(get_list)
125 125
126 126 def get_spstr(self):
127 127 try:
128 128 return self.__spstr
129 129 except AttributeError:
130 130 self.__spstr = ' '.join(self)
131 131 return self.__spstr
132 132
133 133 s = spstr = property(get_spstr)
134 134
135 135 def get_nlstr(self):
136 136 try:
137 137 return self.__nlstr
138 138 except AttributeError:
139 139 self.__nlstr = '\n'.join(self)
140 140 return self.__nlstr
141 141
142 142 n = nlstr = property(get_nlstr)
143 143
144 144 def get_paths(self):
145 145 try:
146 146 return self.__paths
147 147 except AttributeError:
148 148 self.__paths = [path(p) for p in self if os.path.exists(p)]
149 149 return self.__paths
150 150
151 151 p = paths = property(get_paths)
152 152
153 153 def grep(self, pattern, prune = False, field = None):
154 154 """ Return all strings matching 'pattern' (a regex or callable)
155 155
156 156 This is case-insensitive. If prune is true, return all items
157 157 NOT matching the pattern.
158 158
159 159 If field is specified, the match must occur in the specified
160 160 whitespace-separated field.
161 161
162 162 Examples::
163 163
164 164 a.grep( lambda x: x.startswith('C') )
165 165 a.grep('Cha.*log', prune=1)
166 166 a.grep('chm', field=-1)
167 167 """
168 168
169 169 def match_target(s):
170 170 if field is None:
171 171 return s
172 172 parts = s.split()
173 173 try:
174 174 tgt = parts[field]
175 175 return tgt
176 176 except IndexError:
177 177 return ""
178 178
179 179 if isinstance(pattern, py3compat.string_types):
180 180 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
181 181 else:
182 182 pred = pattern
183 183 if not prune:
184 184 return SList([el for el in self if pred(match_target(el))])
185 185 else:
186 186 return SList([el for el in self if not pred(match_target(el))])
187 187
188 188 def fields(self, *fields):
189 189 """ Collect whitespace-separated fields from string list
190 190
191 191 Allows quick awk-like usage of string lists.
192 192
193 193 Example data (in var a, created by 'a = !ls -l')::
194 194
195 195 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
196 196 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
197 197
198 198 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
199 199 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
200 200 (note the joining by space).
201 201 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
202 202
203 203 IndexErrors are ignored.
204 204
205 205 Without args, fields() just split()'s the strings.
206 206 """
207 207 if len(fields) == 0:
208 208 return [el.split() for el in self]
209 209
210 210 res = SList()
211 211 for el in [f.split() for f in self]:
212 212 lineparts = []
213 213
214 214 for fd in fields:
215 215 try:
216 216 lineparts.append(el[fd])
217 217 except IndexError:
218 218 pass
219 219 if lineparts:
220 220 res.append(" ".join(lineparts))
221 221
222 222 return res
223 223
224 224 def sort(self,field= None, nums = False):
225 225 """ sort by specified fields (see fields())
226 226
227 227 Example::
228 228
229 229 a.sort(1, nums = True)
230 230
231 231 Sorts a by second field, in numerical order (so that 21 > 3)
232 232
233 233 """
234 234
235 235 #decorate, sort, undecorate
236 236 if field is not None:
237 237 dsu = [[SList([line]).fields(field), line] for line in self]
238 238 else:
239 239 dsu = [[line, line] for line in self]
240 240 if nums:
241 241 for i in range(len(dsu)):
242 242 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
243 243 try:
244 244 n = int(numstr)
245 245 except ValueError:
246 246 n = 0;
247 247 dsu[i][0] = n
248 248
249 249
250 250 dsu.sort()
251 251 return SList([t[1] for t in dsu])
252 252
253 253
254 254 # FIXME: We need to reimplement type specific displayhook and then add this
255 255 # back as a custom printer. This should also be moved outside utils into the
256 256 # core.
257 257
258 258 # def print_slist(arg):
259 259 # """ Prettier (non-repr-like) and more informative printer for SList """
260 260 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
261 261 # if hasattr(arg, 'hideonce') and arg.hideonce:
262 262 # arg.hideonce = False
263 263 # return
264 264 #
265 265 # nlprint(arg) # This was a nested list printer, now removed.
266 266 #
267 267 # print_slist = result_display.when_type(SList)(print_slist)
268 268
269 269
270 270 def indent(instr,nspaces=4, ntabs=0, flatten=False):
271 271 """Indent a string a given number of spaces or tabstops.
272 272
273 273 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
274 274
275 275 Parameters
276 276 ----------
277 277
278 278 instr : basestring
279 279 The string to be indented.
280 280 nspaces : int (default: 4)
281 281 The number of spaces to be indented.
282 282 ntabs : int (default: 0)
283 283 The number of tabs to be indented.
284 284 flatten : bool (default: False)
285 285 Whether to scrub existing indentation. If True, all lines will be
286 286 aligned to the same indentation. If False, existing indentation will
287 287 be strictly increased.
288 288
289 289 Returns
290 290 -------
291 291
292 292 str|unicode : string indented by ntabs and nspaces.
293 293
294 294 """
295 295 if instr is None:
296 296 return
297 297 ind = '\t'*ntabs+' '*nspaces
298 298 if flatten:
299 299 pat = re.compile(r'^\s*', re.MULTILINE)
300 300 else:
301 301 pat = re.compile(r'^', re.MULTILINE)
302 302 outstr = re.sub(pat, ind, instr)
303 303 if outstr.endswith(os.linesep+ind):
304 304 return outstr[:-len(ind)]
305 305 else:
306 306 return outstr
307 307
308 308
309 309 def list_strings(arg):
310 310 """Always return a list of strings, given a string or list of strings
311 311 as input.
312 312
313 313 Examples
314 314 --------
315 315 ::
316 316
317 317 In [7]: list_strings('A single string')
318 318 Out[7]: ['A single string']
319 319
320 320 In [8]: list_strings(['A single string in a list'])
321 321 Out[8]: ['A single string in a list']
322 322
323 323 In [9]: list_strings(['A','list','of','strings'])
324 324 Out[9]: ['A', 'list', 'of', 'strings']
325 325 """
326 326
327 327 if isinstance(arg, py3compat.string_types): return [arg]
328 328 else: return arg
329 329
330 330
331 331 def marquee(txt='',width=78,mark='*'):
332 332 """Return the input string centered in a 'marquee'.
333 333
334 334 Examples
335 335 --------
336 336 ::
337 337
338 338 In [16]: marquee('A test',40)
339 339 Out[16]: '**************** A test ****************'
340 340
341 341 In [17]: marquee('A test',40,'-')
342 342 Out[17]: '---------------- A test ----------------'
343 343
344 344 In [18]: marquee('A test',40,' ')
345 345 Out[18]: ' A test '
346 346
347 347 """
348 348 if not txt:
349 349 return (mark*width)[:width]
350 350 nmark = (width-len(txt)-2)//len(mark)//2
351 351 if nmark < 0: nmark =0
352 352 marks = mark*nmark
353 353 return '%s %s %s' % (marks,txt,marks)
354 354
355 355
356 356 ini_spaces_re = re.compile(r'^(\s+)')
357 357
358 358 def num_ini_spaces(strng):
359 359 """Return the number of initial spaces in a string"""
360 360
361 361 ini_spaces = ini_spaces_re.match(strng)
362 362 if ini_spaces:
363 363 return ini_spaces.end()
364 364 else:
365 365 return 0
366 366
367 367
368 368 def format_screen(strng):
369 369 """Format a string for screen printing.
370 370
371 371 This removes some latex-type format codes."""
372 372 # Paragraph continue
373 373 par_re = re.compile(r'\\$',re.MULTILINE)
374 374 strng = par_re.sub('',strng)
375 375 return strng
376 376
377 377
378 378 def dedent(text):
379 379 """Equivalent of textwrap.dedent that ignores unindented first line.
380 380
381 381 This means it will still dedent strings like:
382 382 '''foo
383 383 is a bar
384 384 '''
385 385
386 386 For use in wrap_paragraphs.
387 387 """
388 388
389 389 if text.startswith('\n'):
390 390 # text starts with blank line, don't ignore the first line
391 391 return textwrap.dedent(text)
392 392
393 393 # split first line
394 394 splits = text.split('\n',1)
395 395 if len(splits) == 1:
396 396 # only one line
397 397 return textwrap.dedent(text)
398 398
399 399 first, rest = splits
400 400 # dedent everything but the first line
401 401 rest = textwrap.dedent(rest)
402 402 return '\n'.join([first, rest])
403 403
404 404
405 405 def wrap_paragraphs(text, ncols=80):
406 406 """Wrap multiple paragraphs to fit a specified width.
407 407
408 408 This is equivalent to textwrap.wrap, but with support for multiple
409 409 paragraphs, as separated by empty lines.
410 410
411 411 Returns
412 412 -------
413 413
414 414 list of complete paragraphs, wrapped to fill `ncols` columns.
415 415 """
416 416 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
417 417 text = dedent(text).strip()
418 418 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
419 419 out_ps = []
420 420 indent_re = re.compile(r'\n\s+', re.MULTILINE)
421 421 for p in paragraphs:
422 422 # presume indentation that survives dedent is meaningful formatting,
423 423 # so don't fill unless text is flush.
424 424 if indent_re.search(p) is None:
425 425 # wrap paragraph
426 426 p = textwrap.fill(p, ncols)
427 427 out_ps.append(p)
428 428 return out_ps
429 429
430 430
431 431 def long_substr(data):
432 432 """Return the longest common substring in a list of strings.
433 433
434 434 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
435 435 """
436 436 substr = ''
437 437 if len(data) > 1 and len(data[0]) > 0:
438 438 for i in range(len(data[0])):
439 439 for j in range(len(data[0])-i+1):
440 440 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
441 441 substr = data[0][i:i+j]
442 442 elif len(data) == 1:
443 443 substr = data[0]
444 444 return substr
445 445
446 446
447 447 def strip_email_quotes(text):
448 448 """Strip leading email quotation characters ('>').
449 449
450 450 Removes any combination of leading '>' interspersed with whitespace that
451 451 appears *identically* in all lines of the input text.
452 452
453 453 Parameters
454 454 ----------
455 455 text : str
456 456
457 457 Examples
458 458 --------
459 459
460 460 Simple uses::
461 461
462 462 In [2]: strip_email_quotes('> > text')
463 463 Out[2]: 'text'
464 464
465 465 In [3]: strip_email_quotes('> > text\\n> > more')
466 466 Out[3]: 'text\\nmore'
467 467
468 468 Note how only the common prefix that appears in all lines is stripped::
469 469
470 470 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
471 471 Out[4]: '> text\\n> more\\nmore...'
472 472
473 473 So if any line has no quote marks ('>') , then none are stripped from any
474 474 of them ::
475 475
476 476 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
477 477 Out[5]: '> > text\\n> > more\\nlast different'
478 478 """
479 479 lines = text.splitlines()
480 480 matches = set()
481 481 for line in lines:
482 482 prefix = re.match(r'^(\s*>[ >]*)', line)
483 483 if prefix:
484 484 matches.add(prefix.group(1))
485 485 else:
486 486 break
487 487 else:
488 488 prefix = long_substr(list(matches))
489 489 if prefix:
490 490 strip = len(prefix)
491 491 text = '\n'.join([ ln[strip:] for ln in lines])
492 492 return text
493 493
494 494
495 495 class EvalFormatter(Formatter):
496 496 """A String Formatter that allows evaluation of simple expressions.
497 497
498 498 Note that this version interprets a : as specifying a format string (as per
499 499 standard string formatting), so if slicing is required, you must explicitly
500 500 create a slice.
501 501
502 502 This is to be used in templating cases, such as the parallel batch
503 503 script templates, where simple arithmetic on arguments is useful.
504 504
505 505 Examples
506 506 --------
507 507 ::
508 508
509 509 In [1]: f = EvalFormatter()
510 510 In [2]: f.format('{n//4}', n=8)
511 511 Out[2]: '2'
512 512
513 513 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
514 514 Out[3]: 'll'
515 515 """
516 516 def get_field(self, name, args, kwargs):
517 517 v = eval(name, kwargs)
518 518 return v, name
519 519
520 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
521 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
522 # above, it should be possible to remove FullEvalFormatter.
520 523
521 524 @skip_doctest_py3
522 525 class FullEvalFormatter(Formatter):
523 526 """A String Formatter that allows evaluation of simple expressions.
524 527
525 528 Any time a format key is not found in the kwargs,
526 529 it will be tried as an expression in the kwargs namespace.
527 530
528 531 Note that this version allows slicing using [1:2], so you cannot specify
529 532 a format string. Use :class:`EvalFormatter` to permit format strings.
530 533
531 534 Examples
532 535 --------
533 536 ::
534 537
535 538 In [1]: f = FullEvalFormatter()
536 539 In [2]: f.format('{n//4}', n=8)
537 540 Out[2]: u'2'
538 541
539 542 In [3]: f.format('{list(range(5))[2:4]}')
540 543 Out[3]: u'[2, 3]'
541 544
542 545 In [4]: f.format('{3*2}')
543 546 Out[4]: u'6'
544 547 """
545 548 # copied from Formatter._vformat with minor changes to allow eval
546 549 # and replace the format_spec code with slicing
547 550 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
548 551 if recursion_depth < 0:
549 552 raise ValueError('Max string recursion exceeded')
550 553 result = []
551 554 for literal_text, field_name, format_spec, conversion in \
552 555 self.parse(format_string):
553 556
554 557 # output the literal text
555 558 if literal_text:
556 559 result.append(literal_text)
557 560
558 561 # if there's a field, output it
559 562 if field_name is not None:
560 563 # this is some markup, find the object and do
561 564 # the formatting
562 565
563 566 if format_spec:
564 567 # override format spec, to allow slicing:
565 568 field_name = ':'.join([field_name, format_spec])
566 569
567 570 # eval the contents of the field for the object
568 571 # to be formatted
569 572 obj = eval(field_name, kwargs)
570 573
571 574 # do any conversion on the resulting object
572 575 obj = self.convert_field(obj, conversion)
573 576
574 577 # format the object and append to the result
575 578 result.append(self.format_field(obj, ''))
576 579
577 580 return u''.join(py3compat.cast_unicode(s) for s in result)
578 581
579 582
580 583 @skip_doctest_py3
581 584 class DollarFormatter(FullEvalFormatter):
582 585 """Formatter allowing Itpl style $foo replacement, for names and attribute
583 586 access only. Standard {foo} replacement also works, and allows full
584 587 evaluation of its arguments.
585 588
586 589 Examples
587 590 --------
588 591 ::
589 592
590 593 In [1]: f = DollarFormatter()
591 594 In [2]: f.format('{n//4}', n=8)
592 595 Out[2]: u'2'
593 596
594 597 In [3]: f.format('23 * 76 is $result', result=23*76)
595 598 Out[3]: u'23 * 76 is 1748'
596 599
597 600 In [4]: f.format('$a or {b}', a=1, b=2)
598 601 Out[4]: u'1 or 2'
599 602 """
600 603 _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)")
601 604 def parse(self, fmt_string):
602 605 for literal_txt, field_name, format_spec, conversion \
603 606 in Formatter.parse(self, fmt_string):
604 607
605 608 # Find $foo patterns in the literal text.
606 609 continue_from = 0
607 610 txt = ""
608 611 for m in self._dollar_pattern.finditer(literal_txt):
609 612 new_txt, new_field = m.group(1,2)
610 613 # $$foo --> $foo
611 614 if new_field.startswith("$"):
612 615 txt += new_txt + new_field
613 616 else:
614 617 yield (txt + new_txt, new_field, "", None)
615 618 txt = ""
616 619 continue_from = m.end()
617 620
618 621 # Re-yield the {foo} style pattern
619 622 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
620 623
621 624 #-----------------------------------------------------------------------------
622 625 # Utils to columnize a list of string
623 626 #-----------------------------------------------------------------------------
624 627
625 628 def _chunks(l, n):
626 629 """Yield successive n-sized chunks from l."""
627 630 for i in py3compat.xrange(0, len(l), n):
628 631 yield l[i:i+n]
629 632
630 633
631 634 def _find_optimal(rlist , separator_size=2 , displaywidth=80):
632 635 """Calculate optimal info to columnize a list of string"""
633 636 for nrow in range(1, len(rlist)+1) :
634 637 chk = list(map(max,_chunks(rlist, nrow)))
635 638 sumlength = sum(chk)
636 639 ncols = len(chk)
637 640 if sumlength+separator_size*(ncols-1) <= displaywidth :
638 641 break;
639 642 return {'columns_numbers' : ncols,
640 643 'optimal_separator_width':(displaywidth - sumlength)/(ncols-1) if (ncols -1) else 0,
641 644 'rows_numbers' : nrow,
642 645 'columns_width' : chk
643 646 }
644 647
645 648
646 649 def _get_or_default(mylist, i, default=None):
647 650 """return list item number, or default if don't exist"""
648 651 if i >= len(mylist):
649 652 return default
650 653 else :
651 654 return mylist[i]
652 655
653 656
654 657 @skip_doctest
655 658 def compute_item_matrix(items, empty=None, *args, **kwargs) :
656 659 """Returns a nested list, and info to columnize items
657 660
658 661 Parameters
659 662 ----------
660 663
661 664 items
662 665 list of strings to columize
663 666 empty : (default None)
664 667 default value to fill list if needed
665 668 separator_size : int (default=2)
666 669 How much caracters will be used as a separation between each columns.
667 670 displaywidth : int (default=80)
668 671 The width of the area onto wich the columns should enter
669 672
670 673 Returns
671 674 -------
672 675
673 676 strings_matrix
674 677
675 678 nested list of string, the outer most list contains as many list as
676 679 rows, the innermost lists have each as many element as colums. If the
677 680 total number of elements in `items` does not equal the product of
678 681 rows*columns, the last element of some lists are filled with `None`.
679 682
680 683 dict_info
681 684 some info to make columnize easier:
682 685
683 686 columns_numbers
684 687 number of columns
685 688 rows_numbers
686 689 number of rows
687 690 columns_width
688 691 list of with of each columns
689 692 optimal_separator_width
690 693 best separator width between columns
691 694
692 695 Examples
693 696 --------
694 697 ::
695 698
696 699 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
697 700 ...: compute_item_matrix(l,displaywidth=12)
698 701 Out[1]:
699 702 ([['aaa', 'f', 'k'],
700 703 ['b', 'g', 'l'],
701 704 ['cc', 'h', None],
702 705 ['d', 'i', None],
703 706 ['eeeee', 'j', None]],
704 707 {'columns_numbers': 3,
705 708 'columns_width': [5, 1, 1],
706 709 'optimal_separator_width': 2,
707 710 'rows_numbers': 5})
708 711 """
709 712 info = _find_optimal(list(map(len, items)), *args, **kwargs)
710 713 nrow, ncol = info['rows_numbers'], info['columns_numbers']
711 714 return ([[ _get_or_default(items, c*nrow+i, default=empty) for c in range(ncol) ] for i in range(nrow) ], info)
712 715
713 716
714 717 def columnize(items, separator=' ', displaywidth=80):
715 718 """ Transform a list of strings into a single string with columns.
716 719
717 720 Parameters
718 721 ----------
719 722 items : sequence of strings
720 723 The strings to process.
721 724
722 725 separator : str, optional [default is two spaces]
723 726 The string that separates columns.
724 727
725 728 displaywidth : int, optional [default is 80]
726 729 Width of the display in number of characters.
727 730
728 731 Returns
729 732 -------
730 733 The formatted string.
731 734 """
732 735 if not items :
733 736 return '\n'
734 737 matrix, info = compute_item_matrix(items, separator_size=len(separator), displaywidth=displaywidth)
735 738 fmatrix = [filter(None, x) for x in matrix]
736 739 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])])
737 740 return '\n'.join(map(sjoin, fmatrix))+'\n'
738 741
739 742
740 743 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
741 744 """
742 745 Return a string with a natural enumeration of items
743 746
744 747 >>> get_text_list(['a', 'b', 'c', 'd'])
745 748 'a, b, c and d'
746 749 >>> get_text_list(['a', 'b', 'c'], ' or ')
747 750 'a, b or c'
748 751 >>> get_text_list(['a', 'b', 'c'], ', ')
749 752 'a, b, c'
750 753 >>> get_text_list(['a', 'b'], ' or ')
751 754 'a or b'
752 755 >>> get_text_list(['a'])
753 756 'a'
754 757 >>> get_text_list([])
755 758 ''
756 759 >>> get_text_list(['a', 'b'], wrap_item_with="`")
757 760 '`a` and `b`'
758 761 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
759 762 'a + b + c = d'
760 763 """
761 764 if len(list_) == 0:
762 765 return ''
763 766 if wrap_item_with:
764 767 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
765 768 item in list_]
766 769 if len(list_) == 1:
767 770 return list_[0]
768 771 return '%s%s%s' % (
769 772 sep.join(i for i in list_[:-1]),
770 773 last_sep, list_[-1]) No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now