##// END OF EJS Templates
Fix bug where tab-completion with very long filenames would crash the qt console....
Fernando Perez -
Show More
@@ -1,30 +1,44 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.text"""
2 """Tests for IPython.utils.text"""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2011 The IPython Development Team
5 # Copyright (C) 2011 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import os
15 import os
16
16
17 import nose.tools as nt
17 import nose.tools as nt
18
18
19 from nose import with_setup
19 from nose import with_setup
20
20
21 from IPython.testing import decorators as dec
21 from IPython.testing import decorators as dec
22 from IPython.utils import text
22 from IPython.utils import text
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Globals
25 # Globals
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 def test_columnize():
28 def test_columnize():
29 """Test columnize with very long inputs"""
29 """Basic columnize tests."""
30 text.columnize(['a'*180, 'b'*180])
30 size = 5
31 items = [l*size for l in 'abc']
32 out = text.columnize(items, displaywidth=80)
33 nt.assert_equals(out, 'aaaaa bbbbb ccccc\n')
34 out = text.columnize(items, displaywidth=10)
35 nt.assert_equals(out, 'aaaaa ccccc\nbbbbb\n')
36
37
38 def test_columnize_long():
39 """Test columnize with inputs longer than the display window"""
40 text.columnize(['a'*81, 'b'*81], displaywidth=80)
41 size = 11
42 items = [l*size for l in 'abc']
43 out = text.columnize(items, displaywidth=size-1)
44 nt.assert_equals(out, '\n'.join(items+['']))
@@ -1,679 +1,686 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
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2009 The IPython Development Team
7 # Copyright (C) 2008-2009 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import __main__
17 import __main__
18
18
19 import os
19 import os
20 import re
20 import re
21 import shutil
21 import shutil
22 import textwrap
22 import textwrap
23 from string import Formatter
23 from string import Formatter
24
24
25 from IPython.external.path import path
25 from IPython.external.path import path
26
26
27 from IPython.utils.io import nlprint
27 from IPython.utils.io import nlprint
28 from IPython.utils.data import flatten
28 from IPython.utils.data import flatten
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Code
31 # Code
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34
34
35 def unquote_ends(istr):
35 def unquote_ends(istr):
36 """Remove a single pair of quotes from the endpoints of a string."""
36 """Remove a single pair of quotes from the endpoints of a string."""
37
37
38 if not istr:
38 if not istr:
39 return istr
39 return istr
40 if (istr[0]=="'" and istr[-1]=="'") or \
40 if (istr[0]=="'" and istr[-1]=="'") or \
41 (istr[0]=='"' and istr[-1]=='"'):
41 (istr[0]=='"' and istr[-1]=='"'):
42 return istr[1:-1]
42 return istr[1:-1]
43 else:
43 else:
44 return istr
44 return istr
45
45
46
46
47 class LSString(str):
47 class LSString(str):
48 """String derivative with a special access attributes.
48 """String derivative with a special access attributes.
49
49
50 These are normal strings, but with the special attributes:
50 These are normal strings, but with the special attributes:
51
51
52 .l (or .list) : value as list (split on newlines).
52 .l (or .list) : value as list (split on newlines).
53 .n (or .nlstr): original value (the string itself).
53 .n (or .nlstr): original value (the string itself).
54 .s (or .spstr): value as whitespace-separated string.
54 .s (or .spstr): value as whitespace-separated string.
55 .p (or .paths): list of path objects
55 .p (or .paths): list of path objects
56
56
57 Any values which require transformations are computed only once and
57 Any values which require transformations are computed only once and
58 cached.
58 cached.
59
59
60 Such strings are very useful to efficiently interact with the shell, which
60 Such strings are very useful to efficiently interact with the shell, which
61 typically only understands whitespace-separated options for commands."""
61 typically only understands whitespace-separated options for commands."""
62
62
63 def get_list(self):
63 def get_list(self):
64 try:
64 try:
65 return self.__list
65 return self.__list
66 except AttributeError:
66 except AttributeError:
67 self.__list = self.split('\n')
67 self.__list = self.split('\n')
68 return self.__list
68 return self.__list
69
69
70 l = list = property(get_list)
70 l = list = property(get_list)
71
71
72 def get_spstr(self):
72 def get_spstr(self):
73 try:
73 try:
74 return self.__spstr
74 return self.__spstr
75 except AttributeError:
75 except AttributeError:
76 self.__spstr = self.replace('\n',' ')
76 self.__spstr = self.replace('\n',' ')
77 return self.__spstr
77 return self.__spstr
78
78
79 s = spstr = property(get_spstr)
79 s = spstr = property(get_spstr)
80
80
81 def get_nlstr(self):
81 def get_nlstr(self):
82 return self
82 return self
83
83
84 n = nlstr = property(get_nlstr)
84 n = nlstr = property(get_nlstr)
85
85
86 def get_paths(self):
86 def get_paths(self):
87 try:
87 try:
88 return self.__paths
88 return self.__paths
89 except AttributeError:
89 except AttributeError:
90 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
90 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
91 return self.__paths
91 return self.__paths
92
92
93 p = paths = property(get_paths)
93 p = paths = property(get_paths)
94
94
95 # FIXME: We need to reimplement type specific displayhook and then add this
95 # FIXME: We need to reimplement type specific displayhook and then add this
96 # back as a custom printer. This should also be moved outside utils into the
96 # back as a custom printer. This should also be moved outside utils into the
97 # core.
97 # core.
98
98
99 # def print_lsstring(arg):
99 # def print_lsstring(arg):
100 # """ Prettier (non-repr-like) and more informative printer for LSString """
100 # """ Prettier (non-repr-like) and more informative printer for LSString """
101 # print "LSString (.p, .n, .l, .s available). Value:"
101 # print "LSString (.p, .n, .l, .s available). Value:"
102 # print arg
102 # print arg
103 #
103 #
104 #
104 #
105 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
105 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
106
106
107
107
108 class SList(list):
108 class SList(list):
109 """List derivative with a special access attributes.
109 """List derivative with a special access attributes.
110
110
111 These are normal lists, but with the special attributes:
111 These are normal lists, but with the special attributes:
112
112
113 .l (or .list) : value as list (the list itself).
113 .l (or .list) : value as list (the list itself).
114 .n (or .nlstr): value as a string, joined on newlines.
114 .n (or .nlstr): value as a string, joined on newlines.
115 .s (or .spstr): value as a string, joined on spaces.
115 .s (or .spstr): value as a string, joined on spaces.
116 .p (or .paths): list of path objects
116 .p (or .paths): list of path objects
117
117
118 Any values which require transformations are computed only once and
118 Any values which require transformations are computed only once and
119 cached."""
119 cached."""
120
120
121 def get_list(self):
121 def get_list(self):
122 return self
122 return self
123
123
124 l = list = property(get_list)
124 l = list = property(get_list)
125
125
126 def get_spstr(self):
126 def get_spstr(self):
127 try:
127 try:
128 return self.__spstr
128 return self.__spstr
129 except AttributeError:
129 except AttributeError:
130 self.__spstr = ' '.join(self)
130 self.__spstr = ' '.join(self)
131 return self.__spstr
131 return self.__spstr
132
132
133 s = spstr = property(get_spstr)
133 s = spstr = property(get_spstr)
134
134
135 def get_nlstr(self):
135 def get_nlstr(self):
136 try:
136 try:
137 return self.__nlstr
137 return self.__nlstr
138 except AttributeError:
138 except AttributeError:
139 self.__nlstr = '\n'.join(self)
139 self.__nlstr = '\n'.join(self)
140 return self.__nlstr
140 return self.__nlstr
141
141
142 n = nlstr = property(get_nlstr)
142 n = nlstr = property(get_nlstr)
143
143
144 def get_paths(self):
144 def get_paths(self):
145 try:
145 try:
146 return self.__paths
146 return self.__paths
147 except AttributeError:
147 except AttributeError:
148 self.__paths = [path(p) for p in self if os.path.exists(p)]
148 self.__paths = [path(p) for p in self if os.path.exists(p)]
149 return self.__paths
149 return self.__paths
150
150
151 p = paths = property(get_paths)
151 p = paths = property(get_paths)
152
152
153 def grep(self, pattern, prune = False, field = None):
153 def grep(self, pattern, prune = False, field = None):
154 """ Return all strings matching 'pattern' (a regex or callable)
154 """ Return all strings matching 'pattern' (a regex or callable)
155
155
156 This is case-insensitive. If prune is true, return all items
156 This is case-insensitive. If prune is true, return all items
157 NOT matching the pattern.
157 NOT matching the pattern.
158
158
159 If field is specified, the match must occur in the specified
159 If field is specified, the match must occur in the specified
160 whitespace-separated field.
160 whitespace-separated field.
161
161
162 Examples::
162 Examples::
163
163
164 a.grep( lambda x: x.startswith('C') )
164 a.grep( lambda x: x.startswith('C') )
165 a.grep('Cha.*log', prune=1)
165 a.grep('Cha.*log', prune=1)
166 a.grep('chm', field=-1)
166 a.grep('chm', field=-1)
167 """
167 """
168
168
169 def match_target(s):
169 def match_target(s):
170 if field is None:
170 if field is None:
171 return s
171 return s
172 parts = s.split()
172 parts = s.split()
173 try:
173 try:
174 tgt = parts[field]
174 tgt = parts[field]
175 return tgt
175 return tgt
176 except IndexError:
176 except IndexError:
177 return ""
177 return ""
178
178
179 if isinstance(pattern, basestring):
179 if isinstance(pattern, basestring):
180 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
180 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
181 else:
181 else:
182 pred = pattern
182 pred = pattern
183 if not prune:
183 if not prune:
184 return SList([el for el in self if pred(match_target(el))])
184 return SList([el for el in self if pred(match_target(el))])
185 else:
185 else:
186 return SList([el for el in self if not pred(match_target(el))])
186 return SList([el for el in self if not pred(match_target(el))])
187
187
188 def fields(self, *fields):
188 def fields(self, *fields):
189 """ Collect whitespace-separated fields from string list
189 """ Collect whitespace-separated fields from string list
190
190
191 Allows quick awk-like usage of string lists.
191 Allows quick awk-like usage of string lists.
192
192
193 Example data (in var a, created by 'a = !ls -l')::
193 Example data (in var a, created by 'a = !ls -l')::
194 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
194 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
195 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
195 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
196
196
197 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
197 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
198 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
198 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
199 (note the joining by space).
199 (note the joining by space).
200 a.fields(-1) is ['ChangeLog', 'IPython']
200 a.fields(-1) is ['ChangeLog', 'IPython']
201
201
202 IndexErrors are ignored.
202 IndexErrors are ignored.
203
203
204 Without args, fields() just split()'s the strings.
204 Without args, fields() just split()'s the strings.
205 """
205 """
206 if len(fields) == 0:
206 if len(fields) == 0:
207 return [el.split() for el in self]
207 return [el.split() for el in self]
208
208
209 res = SList()
209 res = SList()
210 for el in [f.split() for f in self]:
210 for el in [f.split() for f in self]:
211 lineparts = []
211 lineparts = []
212
212
213 for fd in fields:
213 for fd in fields:
214 try:
214 try:
215 lineparts.append(el[fd])
215 lineparts.append(el[fd])
216 except IndexError:
216 except IndexError:
217 pass
217 pass
218 if lineparts:
218 if lineparts:
219 res.append(" ".join(lineparts))
219 res.append(" ".join(lineparts))
220
220
221 return res
221 return res
222
222
223 def sort(self,field= None, nums = False):
223 def sort(self,field= None, nums = False):
224 """ sort by specified fields (see fields())
224 """ sort by specified fields (see fields())
225
225
226 Example::
226 Example::
227 a.sort(1, nums = True)
227 a.sort(1, nums = True)
228
228
229 Sorts a by second field, in numerical order (so that 21 > 3)
229 Sorts a by second field, in numerical order (so that 21 > 3)
230
230
231 """
231 """
232
232
233 #decorate, sort, undecorate
233 #decorate, sort, undecorate
234 if field is not None:
234 if field is not None:
235 dsu = [[SList([line]).fields(field), line] for line in self]
235 dsu = [[SList([line]).fields(field), line] for line in self]
236 else:
236 else:
237 dsu = [[line, line] for line in self]
237 dsu = [[line, line] for line in self]
238 if nums:
238 if nums:
239 for i in range(len(dsu)):
239 for i in range(len(dsu)):
240 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
240 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
241 try:
241 try:
242 n = int(numstr)
242 n = int(numstr)
243 except ValueError:
243 except ValueError:
244 n = 0;
244 n = 0;
245 dsu[i][0] = n
245 dsu[i][0] = n
246
246
247
247
248 dsu.sort()
248 dsu.sort()
249 return SList([t[1] for t in dsu])
249 return SList([t[1] for t in dsu])
250
250
251
251
252 # FIXME: We need to reimplement type specific displayhook and then add this
252 # FIXME: We need to reimplement type specific displayhook and then add this
253 # back as a custom printer. This should also be moved outside utils into the
253 # back as a custom printer. This should also be moved outside utils into the
254 # core.
254 # core.
255
255
256 # def print_slist(arg):
256 # def print_slist(arg):
257 # """ Prettier (non-repr-like) and more informative printer for SList """
257 # """ Prettier (non-repr-like) and more informative printer for SList """
258 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
258 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
259 # if hasattr(arg, 'hideonce') and arg.hideonce:
259 # if hasattr(arg, 'hideonce') and arg.hideonce:
260 # arg.hideonce = False
260 # arg.hideonce = False
261 # return
261 # return
262 #
262 #
263 # nlprint(arg)
263 # nlprint(arg)
264 #
264 #
265 # print_slist = result_display.when_type(SList)(print_slist)
265 # print_slist = result_display.when_type(SList)(print_slist)
266
266
267
267
268 def esc_quotes(strng):
268 def esc_quotes(strng):
269 """Return the input string with single and double quotes escaped out"""
269 """Return the input string with single and double quotes escaped out"""
270
270
271 return strng.replace('"','\\"').replace("'","\\'")
271 return strng.replace('"','\\"').replace("'","\\'")
272
272
273
273
274 def make_quoted_expr(s):
274 def make_quoted_expr(s):
275 """Return string s in appropriate quotes, using raw string if possible.
275 """Return string s in appropriate quotes, using raw string if possible.
276
276
277 XXX - example removed because it caused encoding errors in documentation
277 XXX - example removed because it caused encoding errors in documentation
278 generation. We need a new example that doesn't contain invalid chars.
278 generation. We need a new example that doesn't contain invalid chars.
279
279
280 Note the use of raw string and padding at the end to allow trailing
280 Note the use of raw string and padding at the end to allow trailing
281 backslash.
281 backslash.
282 """
282 """
283
283
284 tail = ''
284 tail = ''
285 tailpadding = ''
285 tailpadding = ''
286 raw = ''
286 raw = ''
287 ucode = 'u'
287 ucode = 'u'
288 if "\\" in s:
288 if "\\" in s:
289 raw = 'r'
289 raw = 'r'
290 if s.endswith('\\'):
290 if s.endswith('\\'):
291 tail = '[:-1]'
291 tail = '[:-1]'
292 tailpadding = '_'
292 tailpadding = '_'
293 if '"' not in s:
293 if '"' not in s:
294 quote = '"'
294 quote = '"'
295 elif "'" not in s:
295 elif "'" not in s:
296 quote = "'"
296 quote = "'"
297 elif '"""' not in s and not s.endswith('"'):
297 elif '"""' not in s and not s.endswith('"'):
298 quote = '"""'
298 quote = '"""'
299 elif "'''" not in s and not s.endswith("'"):
299 elif "'''" not in s and not s.endswith("'"):
300 quote = "'''"
300 quote = "'''"
301 else:
301 else:
302 # give up, backslash-escaped string will do
302 # give up, backslash-escaped string will do
303 return '"%s"' % esc_quotes(s)
303 return '"%s"' % esc_quotes(s)
304 res = ucode + raw + quote + s + tailpadding + quote + tail
304 res = ucode + raw + quote + s + tailpadding + quote + tail
305 return res
305 return res
306
306
307
307
308 def qw(words,flat=0,sep=None,maxsplit=-1):
308 def qw(words,flat=0,sep=None,maxsplit=-1):
309 """Similar to Perl's qw() operator, but with some more options.
309 """Similar to Perl's qw() operator, but with some more options.
310
310
311 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
311 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
312
312
313 words can also be a list itself, and with flat=1, the output will be
313 words can also be a list itself, and with flat=1, the output will be
314 recursively flattened.
314 recursively flattened.
315
315
316 Examples:
316 Examples:
317
317
318 >>> qw('1 2')
318 >>> qw('1 2')
319 ['1', '2']
319 ['1', '2']
320
320
321 >>> qw(['a b','1 2',['m n','p q']])
321 >>> qw(['a b','1 2',['m n','p q']])
322 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
322 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
323
323
324 >>> qw(['a b','1 2',['m n','p q']],flat=1)
324 >>> qw(['a b','1 2',['m n','p q']],flat=1)
325 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
325 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
326 """
326 """
327
327
328 if isinstance(words, basestring):
328 if isinstance(words, basestring):
329 return [word.strip() for word in words.split(sep,maxsplit)
329 return [word.strip() for word in words.split(sep,maxsplit)
330 if word and not word.isspace() ]
330 if word and not word.isspace() ]
331 if flat:
331 if flat:
332 return flatten(map(qw,words,[1]*len(words)))
332 return flatten(map(qw,words,[1]*len(words)))
333 return map(qw,words)
333 return map(qw,words)
334
334
335
335
336 def qwflat(words,sep=None,maxsplit=-1):
336 def qwflat(words,sep=None,maxsplit=-1):
337 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
337 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
338 return qw(words,1,sep,maxsplit)
338 return qw(words,1,sep,maxsplit)
339
339
340
340
341 def qw_lol(indata):
341 def qw_lol(indata):
342 """qw_lol('a b') -> [['a','b']],
342 """qw_lol('a b') -> [['a','b']],
343 otherwise it's just a call to qw().
343 otherwise it's just a call to qw().
344
344
345 We need this to make sure the modules_some keys *always* end up as a
345 We need this to make sure the modules_some keys *always* end up as a
346 list of lists."""
346 list of lists."""
347
347
348 if isinstance(indata, basestring):
348 if isinstance(indata, basestring):
349 return [qw(indata)]
349 return [qw(indata)]
350 else:
350 else:
351 return qw(indata)
351 return qw(indata)
352
352
353
353
354 def grep(pat,list,case=1):
354 def grep(pat,list,case=1):
355 """Simple minded grep-like function.
355 """Simple minded grep-like function.
356 grep(pat,list) returns occurrences of pat in list, None on failure.
356 grep(pat,list) returns occurrences of pat in list, None on failure.
357
357
358 It only does simple string matching, with no support for regexps. Use the
358 It only does simple string matching, with no support for regexps. Use the
359 option case=0 for case-insensitive matching."""
359 option case=0 for case-insensitive matching."""
360
360
361 # This is pretty crude. At least it should implement copying only references
361 # This is pretty crude. At least it should implement copying only references
362 # to the original data in case it's big. Now it copies the data for output.
362 # to the original data in case it's big. Now it copies the data for output.
363 out=[]
363 out=[]
364 if case:
364 if case:
365 for term in list:
365 for term in list:
366 if term.find(pat)>-1: out.append(term)
366 if term.find(pat)>-1: out.append(term)
367 else:
367 else:
368 lpat=pat.lower()
368 lpat=pat.lower()
369 for term in list:
369 for term in list:
370 if term.lower().find(lpat)>-1: out.append(term)
370 if term.lower().find(lpat)>-1: out.append(term)
371
371
372 if len(out): return out
372 if len(out): return out
373 else: return None
373 else: return None
374
374
375
375
376 def dgrep(pat,*opts):
376 def dgrep(pat,*opts):
377 """Return grep() on dir()+dir(__builtins__).
377 """Return grep() on dir()+dir(__builtins__).
378
378
379 A very common use of grep() when working interactively."""
379 A very common use of grep() when working interactively."""
380
380
381 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
381 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
382
382
383
383
384 def idgrep(pat):
384 def idgrep(pat):
385 """Case-insensitive dgrep()"""
385 """Case-insensitive dgrep()"""
386
386
387 return dgrep(pat,0)
387 return dgrep(pat,0)
388
388
389
389
390 def igrep(pat,list):
390 def igrep(pat,list):
391 """Synonym for case-insensitive grep."""
391 """Synonym for case-insensitive grep."""
392
392
393 return grep(pat,list,case=0)
393 return grep(pat,list,case=0)
394
394
395
395
396 def indent(instr,nspaces=4, ntabs=0, flatten=False):
396 def indent(instr,nspaces=4, ntabs=0, flatten=False):
397 """Indent a string a given number of spaces or tabstops.
397 """Indent a string a given number of spaces or tabstops.
398
398
399 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
399 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
400
400
401 Parameters
401 Parameters
402 ----------
402 ----------
403
403
404 instr : basestring
404 instr : basestring
405 The string to be indented.
405 The string to be indented.
406 nspaces : int (default: 4)
406 nspaces : int (default: 4)
407 The number of spaces to be indented.
407 The number of spaces to be indented.
408 ntabs : int (default: 0)
408 ntabs : int (default: 0)
409 The number of tabs to be indented.
409 The number of tabs to be indented.
410 flatten : bool (default: False)
410 flatten : bool (default: False)
411 Whether to scrub existing indentation. If True, all lines will be
411 Whether to scrub existing indentation. If True, all lines will be
412 aligned to the same indentation. If False, existing indentation will
412 aligned to the same indentation. If False, existing indentation will
413 be strictly increased.
413 be strictly increased.
414
414
415 Returns
415 Returns
416 -------
416 -------
417
417
418 str|unicode : string indented by ntabs and nspaces.
418 str|unicode : string indented by ntabs and nspaces.
419
419
420 """
420 """
421 if instr is None:
421 if instr is None:
422 return
422 return
423 ind = '\t'*ntabs+' '*nspaces
423 ind = '\t'*ntabs+' '*nspaces
424 if flatten:
424 if flatten:
425 pat = re.compile(r'^\s*', re.MULTILINE)
425 pat = re.compile(r'^\s*', re.MULTILINE)
426 else:
426 else:
427 pat = re.compile(r'^', re.MULTILINE)
427 pat = re.compile(r'^', re.MULTILINE)
428 outstr = re.sub(pat, ind, instr)
428 outstr = re.sub(pat, ind, instr)
429 if outstr.endswith(os.linesep+ind):
429 if outstr.endswith(os.linesep+ind):
430 return outstr[:-len(ind)]
430 return outstr[:-len(ind)]
431 else:
431 else:
432 return outstr
432 return outstr
433
433
434 def native_line_ends(filename,backup=1):
434 def native_line_ends(filename,backup=1):
435 """Convert (in-place) a file to line-ends native to the current OS.
435 """Convert (in-place) a file to line-ends native to the current OS.
436
436
437 If the optional backup argument is given as false, no backup of the
437 If the optional backup argument is given as false, no backup of the
438 original file is left. """
438 original file is left. """
439
439
440 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
440 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
441
441
442 bak_filename = filename + backup_suffixes[os.name]
442 bak_filename = filename + backup_suffixes[os.name]
443
443
444 original = open(filename).read()
444 original = open(filename).read()
445 shutil.copy2(filename,bak_filename)
445 shutil.copy2(filename,bak_filename)
446 try:
446 try:
447 new = open(filename,'wb')
447 new = open(filename,'wb')
448 new.write(os.linesep.join(original.splitlines()))
448 new.write(os.linesep.join(original.splitlines()))
449 new.write(os.linesep) # ALWAYS put an eol at the end of the file
449 new.write(os.linesep) # ALWAYS put an eol at the end of the file
450 new.close()
450 new.close()
451 except:
451 except:
452 os.rename(bak_filename,filename)
452 os.rename(bak_filename,filename)
453 if not backup:
453 if not backup:
454 try:
454 try:
455 os.remove(bak_filename)
455 os.remove(bak_filename)
456 except:
456 except:
457 pass
457 pass
458
458
459
459
460 def list_strings(arg):
460 def list_strings(arg):
461 """Always return a list of strings, given a string or list of strings
461 """Always return a list of strings, given a string or list of strings
462 as input.
462 as input.
463
463
464 :Examples:
464 :Examples:
465
465
466 In [7]: list_strings('A single string')
466 In [7]: list_strings('A single string')
467 Out[7]: ['A single string']
467 Out[7]: ['A single string']
468
468
469 In [8]: list_strings(['A single string in a list'])
469 In [8]: list_strings(['A single string in a list'])
470 Out[8]: ['A single string in a list']
470 Out[8]: ['A single string in a list']
471
471
472 In [9]: list_strings(['A','list','of','strings'])
472 In [9]: list_strings(['A','list','of','strings'])
473 Out[9]: ['A', 'list', 'of', 'strings']
473 Out[9]: ['A', 'list', 'of', 'strings']
474 """
474 """
475
475
476 if isinstance(arg,basestring): return [arg]
476 if isinstance(arg,basestring): return [arg]
477 else: return arg
477 else: return arg
478
478
479
479
480 def marquee(txt='',width=78,mark='*'):
480 def marquee(txt='',width=78,mark='*'):
481 """Return the input string centered in a 'marquee'.
481 """Return the input string centered in a 'marquee'.
482
482
483 :Examples:
483 :Examples:
484
484
485 In [16]: marquee('A test',40)
485 In [16]: marquee('A test',40)
486 Out[16]: '**************** A test ****************'
486 Out[16]: '**************** A test ****************'
487
487
488 In [17]: marquee('A test',40,'-')
488 In [17]: marquee('A test',40,'-')
489 Out[17]: '---------------- A test ----------------'
489 Out[17]: '---------------- A test ----------------'
490
490
491 In [18]: marquee('A test',40,' ')
491 In [18]: marquee('A test',40,' ')
492 Out[18]: ' A test '
492 Out[18]: ' A test '
493
493
494 """
494 """
495 if not txt:
495 if not txt:
496 return (mark*width)[:width]
496 return (mark*width)[:width]
497 nmark = (width-len(txt)-2)/len(mark)/2
497 nmark = (width-len(txt)-2)/len(mark)/2
498 if nmark < 0: nmark =0
498 if nmark < 0: nmark =0
499 marks = mark*nmark
499 marks = mark*nmark
500 return '%s %s %s' % (marks,txt,marks)
500 return '%s %s %s' % (marks,txt,marks)
501
501
502
502
503 ini_spaces_re = re.compile(r'^(\s+)')
503 ini_spaces_re = re.compile(r'^(\s+)')
504
504
505 def num_ini_spaces(strng):
505 def num_ini_spaces(strng):
506 """Return the number of initial spaces in a string"""
506 """Return the number of initial spaces in a string"""
507
507
508 ini_spaces = ini_spaces_re.match(strng)
508 ini_spaces = ini_spaces_re.match(strng)
509 if ini_spaces:
509 if ini_spaces:
510 return ini_spaces.end()
510 return ini_spaces.end()
511 else:
511 else:
512 return 0
512 return 0
513
513
514
514
515 def format_screen(strng):
515 def format_screen(strng):
516 """Format a string for screen printing.
516 """Format a string for screen printing.
517
517
518 This removes some latex-type format codes."""
518 This removes some latex-type format codes."""
519 # Paragraph continue
519 # Paragraph continue
520 par_re = re.compile(r'\\$',re.MULTILINE)
520 par_re = re.compile(r'\\$',re.MULTILINE)
521 strng = par_re.sub('',strng)
521 strng = par_re.sub('',strng)
522 return strng
522 return strng
523
523
524 def dedent(text):
524 def dedent(text):
525 """Equivalent of textwrap.dedent that ignores unindented first line.
525 """Equivalent of textwrap.dedent that ignores unindented first line.
526
526
527 This means it will still dedent strings like:
527 This means it will still dedent strings like:
528 '''foo
528 '''foo
529 is a bar
529 is a bar
530 '''
530 '''
531
531
532 For use in wrap_paragraphs.
532 For use in wrap_paragraphs.
533 """
533 """
534
534
535 if text.startswith('\n'):
535 if text.startswith('\n'):
536 # text starts with blank line, don't ignore the first line
536 # text starts with blank line, don't ignore the first line
537 return textwrap.dedent(text)
537 return textwrap.dedent(text)
538
538
539 # split first line
539 # split first line
540 splits = text.split('\n',1)
540 splits = text.split('\n',1)
541 if len(splits) == 1:
541 if len(splits) == 1:
542 # only one line
542 # only one line
543 return textwrap.dedent(text)
543 return textwrap.dedent(text)
544
544
545 first, rest = splits
545 first, rest = splits
546 # dedent everything but the first line
546 # dedent everything but the first line
547 rest = textwrap.dedent(rest)
547 rest = textwrap.dedent(rest)
548 return '\n'.join([first, rest])
548 return '\n'.join([first, rest])
549
549
550 def wrap_paragraphs(text, ncols=80):
550 def wrap_paragraphs(text, ncols=80):
551 """Wrap multiple paragraphs to fit a specified width.
551 """Wrap multiple paragraphs to fit a specified width.
552
552
553 This is equivalent to textwrap.wrap, but with support for multiple
553 This is equivalent to textwrap.wrap, but with support for multiple
554 paragraphs, as separated by empty lines.
554 paragraphs, as separated by empty lines.
555
555
556 Returns
556 Returns
557 -------
557 -------
558
558
559 list of complete paragraphs, wrapped to fill `ncols` columns.
559 list of complete paragraphs, wrapped to fill `ncols` columns.
560 """
560 """
561 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
561 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
562 text = dedent(text).strip()
562 text = dedent(text).strip()
563 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
563 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
564 out_ps = []
564 out_ps = []
565 indent_re = re.compile(r'\n\s+', re.MULTILINE)
565 indent_re = re.compile(r'\n\s+', re.MULTILINE)
566 for p in paragraphs:
566 for p in paragraphs:
567 # presume indentation that survives dedent is meaningful formatting,
567 # presume indentation that survives dedent is meaningful formatting,
568 # so don't fill unless text is flush.
568 # so don't fill unless text is flush.
569 if indent_re.search(p) is None:
569 if indent_re.search(p) is None:
570 # wrap paragraph
570 # wrap paragraph
571 p = textwrap.fill(p, ncols)
571 p = textwrap.fill(p, ncols)
572 out_ps.append(p)
572 out_ps.append(p)
573 return out_ps
573 return out_ps
574
574
575
575
576
576
577 class EvalFormatter(Formatter):
577 class EvalFormatter(Formatter):
578 """A String Formatter that allows evaluation of simple expressions.
578 """A String Formatter that allows evaluation of simple expressions.
579
579
580 Any time a format key is not found in the kwargs,
580 Any time a format key is not found in the kwargs,
581 it will be tried as an expression in the kwargs namespace.
581 it will be tried as an expression in the kwargs namespace.
582
582
583 This is to be used in templating cases, such as the parallel batch
583 This is to be used in templating cases, such as the parallel batch
584 script templates, where simple arithmetic on arguments is useful.
584 script templates, where simple arithmetic on arguments is useful.
585
585
586 Examples
586 Examples
587 --------
587 --------
588
588
589 In [1]: f = EvalFormatter()
589 In [1]: f = EvalFormatter()
590 In [2]: f.format('{n/4}', n=8)
590 In [2]: f.format('{n/4}', n=8)
591 Out[2]: '2'
591 Out[2]: '2'
592
592
593 In [3]: f.format('{range(3)}')
593 In [3]: f.format('{range(3)}')
594 Out[3]: '[0, 1, 2]'
594 Out[3]: '[0, 1, 2]'
595
595
596 In [4]: f.format('{3*2}')
596 In [4]: f.format('{3*2}')
597 Out[4]: '6'
597 Out[4]: '6'
598 """
598 """
599
599
600 def get_value(self, key, args, kwargs):
600 def get_value(self, key, args, kwargs):
601 if isinstance(key, (int, long)):
601 if isinstance(key, (int, long)):
602 return args[key]
602 return args[key]
603 elif key in kwargs:
603 elif key in kwargs:
604 return kwargs[key]
604 return kwargs[key]
605 else:
605 else:
606 # evaluate the expression using kwargs as namespace
606 # evaluate the expression using kwargs as namespace
607 try:
607 try:
608 return eval(key, kwargs)
608 return eval(key, kwargs)
609 except Exception:
609 except Exception:
610 # classify all bad expressions as key errors
610 # classify all bad expressions as key errors
611 raise KeyError(key)
611 raise KeyError(key)
612
612
613
613
614 def columnize(items, separator=' ', displaywidth=80):
614 def columnize(items, separator=' ', displaywidth=80):
615 """ Transform a list of strings into a single string with columns.
615 """ Transform a list of strings into a single string with columns.
616
616
617 Parameters
617 Parameters
618 ----------
618 ----------
619 items : sequence of strings
619 items : sequence of strings
620 The strings to process.
620 The strings to process.
621
621
622 separator : str, optional [default is two spaces]
622 separator : str, optional [default is two spaces]
623 The string that separates columns.
623 The string that separates columns.
624
624
625 displaywidth : int, optional [default is 80]
625 displaywidth : int, optional [default is 80]
626 Width of the display in number of characters.
626 Width of the display in number of characters.
627
627
628 Returns
628 Returns
629 -------
629 -------
630 The formatted string.
630 The formatted string.
631 """
631 """
632 # Note: this code is adapted from columnize 0.3.2.
632 # Note: this code is adapted from columnize 0.3.2.
633 # See http://code.google.com/p/pycolumnize/
633 # See http://code.google.com/p/pycolumnize/
634
634
635 # Some degenerate cases.
635 # Some degenerate cases.
636 size = len(items)
636 size = len(items)
637 if size == 0:
637 if size == 0:
638 return '\n'
638 return '\n'
639 elif size == 1:
639 elif size == 1:
640 return '%s\n' % items[0]
640 return '%s\n' % items[0]
641
641
642 # Special case: if any item is longer than the maximum width, there's no
643 # point in triggering the logic below...
644 item_len = map(len, items) # save these, we can reuse them below
645 longest = max(item_len)
646 if longest >= displaywidth:
647 return '\n'.join(items+[''])
648
642 # Try every row count from 1 upwards
649 # Try every row count from 1 upwards
643 array_index = lambda nrows, row, col: nrows*col + row
650 array_index = lambda nrows, row, col: nrows*col + row
644 for nrows in range(1, size):
651 for nrows in range(1, size):
645 ncols = (size + nrows - 1) // nrows
652 ncols = (size + nrows - 1) // nrows
646 colwidths = []
653 colwidths = []
647 totwidth = -len(separator)
654 totwidth = -len(separator)
648 for col in range(ncols):
655 for col in range(ncols):
649 # Get max column width for this column
656 # Get max column width for this column
650 colwidth = 0
657 colwidth = 0
651 for row in range(nrows):
658 for row in range(nrows):
652 i = array_index(nrows, row, col)
659 i = array_index(nrows, row, col)
653 if i >= size: break
660 if i >= size: break
654 x = items[i]
661 x, len_x = items[i], item_len[i]
655 colwidth = max(colwidth, len(x))
662 colwidth = max(colwidth, len_x)
656 colwidths.append(colwidth)
663 colwidths.append(colwidth)
657 totwidth += colwidth + len(separator)
664 totwidth += colwidth + len(separator)
658 if totwidth > displaywidth:
665 if totwidth > displaywidth:
659 break
666 break
660 if totwidth <= displaywidth:
667 if totwidth <= displaywidth:
661 break
668 break
662
669
663 # The smallest number of rows computed and the max widths for each
670 # The smallest number of rows computed and the max widths for each
664 # column has been obtained. Now we just have to format each of the rows.
671 # column has been obtained. Now we just have to format each of the rows.
665 string = ''
672 string = ''
666 for row in range(nrows):
673 for row in range(nrows):
667 texts = []
674 texts = []
668 for col in range(ncols):
675 for col in range(ncols):
669 i = row + nrows*col
676 i = row + nrows*col
670 if i >= size:
677 if i >= size:
671 texts.append('')
678 texts.append('')
672 else:
679 else:
673 texts.append(items[i])
680 texts.append(items[i])
674 while texts and not texts[-1]:
681 while texts and not texts[-1]:
675 del texts[-1]
682 del texts[-1]
676 for col in range(len(texts)):
683 for col in range(len(texts)):
677 texts[col] = texts[col].ljust(colwidths[col])
684 texts[col] = texts[col].ljust(colwidths[col])
678 string += '%s\n' % separator.join(texts)
685 string += '%s\n' % separator.join(texts)
679 return string
686 return string
General Comments 0
You need to be logged in to leave comments. Login now