1
|
1 |
"""Text wrapping and filling.
|
|
2 |
"""
|
|
3 |
|
|
4 |
# Copyright (C) 1999-2001 Gregory P. Ward.
|
|
5 |
# Copyright (C) 2002, 2003 Python Software Foundation.
|
|
6 |
# Written by Greg Ward <gward@python.net>
|
|
7 |
|
|
8 |
__revision__ = "$Id: textwrap.py,v 1.1 2009/02/05 23:03:30 stechong Exp $"
|
|
9 |
|
|
10 |
import string, re
|
|
11 |
import types
|
|
12 |
|
|
13 |
# Do the right thing with boolean values for all known Python versions
|
|
14 |
# (so this module can be copied to projects that don't depend on Python
|
|
15 |
# 2.3, e.g. Optik and Docutils).
|
|
16 |
try:
|
|
17 |
True, False
|
|
18 |
except NameError:
|
|
19 |
(True, False) = (1, 0)
|
|
20 |
|
|
21 |
# For Python 1.5, just ignore unicode (try it as str)
|
|
22 |
try:
|
|
23 |
unicode
|
|
24 |
except NameError:
|
|
25 |
unicode=str
|
|
26 |
|
|
27 |
__all__ = ['TextWrapper', 'wrap', 'fill']
|
|
28 |
|
|
29 |
# Hardcode the recognized whitespace characters to the US-ASCII
|
|
30 |
# whitespace characters. The main reason for doing this is that in
|
|
31 |
# ISO-8859-1, 0xa0 is non-breaking whitespace, so in certain locales
|
|
32 |
# that character winds up in string.whitespace. Respecting
|
|
33 |
# string.whitespace in those cases would 1) make textwrap treat 0xa0 the
|
|
34 |
# same as any other whitespace char, which is clearly wrong (it's a
|
|
35 |
# *non-breaking* space), 2) possibly cause problems with Unicode,
|
|
36 |
# since 0xa0 is not in range(128).
|
|
37 |
_whitespace = '\t\n\x0b\x0c\r '
|
|
38 |
|
|
39 |
class TextWrapper:
|
|
40 |
"""
|
|
41 |
Object for wrapping/filling text. The public interface consists of
|
|
42 |
the wrap() and fill() methods; the other methods are just there for
|
|
43 |
subclasses to override in order to tweak the default behaviour.
|
|
44 |
If you want to completely replace the main wrapping algorithm,
|
|
45 |
you'll probably have to override _wrap_chunks().
|
|
46 |
|
|
47 |
Several instance attributes control various aspects of wrapping:
|
|
48 |
width (default: 70)
|
|
49 |
the maximum width of wrapped lines (unless break_long_words
|
|
50 |
is false)
|
|
51 |
initial_indent (default: "")
|
|
52 |
string that will be prepended to the first line of wrapped
|
|
53 |
output. Counts towards the line's width.
|
|
54 |
subsequent_indent (default: "")
|
|
55 |
string that will be prepended to all lines save the first
|
|
56 |
of wrapped output; also counts towards each line's width.
|
|
57 |
expand_tabs (default: true)
|
|
58 |
Expand tabs in input text to spaces before further processing.
|
|
59 |
Each tab will become 1 .. 8 spaces, depending on its position in
|
|
60 |
its line. If false, each tab is treated as a single character.
|
|
61 |
replace_whitespace (default: true)
|
|
62 |
Replace all whitespace characters in the input text by spaces
|
|
63 |
after tab expansion. Note that if expand_tabs is false and
|
|
64 |
replace_whitespace is true, every tab will be converted to a
|
|
65 |
single space!
|
|
66 |
fix_sentence_endings (default: false)
|
|
67 |
Ensure that sentence-ending punctuation is always followed
|
|
68 |
by two spaces. Off by default because the algorithm is
|
|
69 |
(unavoidably) imperfect.
|
|
70 |
break_long_words (default: true)
|
|
71 |
Break words longer than 'width'. If false, those words will not
|
|
72 |
be broken, and some lines might be longer than 'width'.
|
|
73 |
"""
|
|
74 |
|
|
75 |
whitespace_trans = string.maketrans(_whitespace, ' ' * len(_whitespace))
|
|
76 |
|
|
77 |
unicode_whitespace_trans = {}
|
|
78 |
uspace = ord(unicode(' '))
|
|
79 |
for x in map(ord, _whitespace):
|
|
80 |
unicode_whitespace_trans[x] = uspace
|
|
81 |
|
|
82 |
# This funky little regex is just the trick for splitting
|
|
83 |
# text up into word-wrappable chunks. E.g.
|
|
84 |
# "Hello there -- you goof-ball, use the -b option!"
|
|
85 |
# splits into
|
|
86 |
# Hello/ /there/ /--/ /you/ /goof-/ball,/ /use/ /the/ /-b/ /option!
|
|
87 |
# (after stripping out empty strings).
|
|
88 |
try:
|
|
89 |
wordsep_re = re.compile(r'(\s+|' # any whitespace
|
|
90 |
r'[^\s\w]*\w{2,}-(?=\w{2,})|' # hyphenated words
|
|
91 |
r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))') # em-dash
|
|
92 |
except:
|
|
93 |
# Under python 1.5, the above regular expression does not compile because
|
|
94 |
# of positive look-behind assertions (?<=). This stripped down version
|
|
95 |
# does but it causes some regressions in the testsuite. Better than
|
|
96 |
# nothing...
|
|
97 |
wordsep_re = re.compile(r'(\s+|' # any whitespace
|
|
98 |
r'[^\s\w]*\w{2,}-(?=\w{2,})|' # hyphenated words
|
|
99 |
r')') # em-dash
|
|
100 |
|
|
101 |
# XXX this is not locale- or charset-aware -- string.lowercase
|
|
102 |
# is US-ASCII only (and therefore English-only)
|
|
103 |
sentence_end_re = re.compile(r'[%s]' # lowercase letter
|
|
104 |
r'[\.\!\?]' # sentence-ending punct.
|
|
105 |
r'[\"\']?' # optional end-of-quote
|
|
106 |
% string.lowercase)
|
|
107 |
|
|
108 |
|
|
109 |
def __init__(self,
|
|
110 |
width=70,
|
|
111 |
initial_indent="",
|
|
112 |
subsequent_indent="",
|
|
113 |
expand_tabs=True,
|
|
114 |
replace_whitespace=True,
|
|
115 |
fix_sentence_endings=False,
|
|
116 |
break_long_words=True):
|
|
117 |
self.width = width
|
|
118 |
self.initial_indent = initial_indent
|
|
119 |
self.subsequent_indent = subsequent_indent
|
|
120 |
self.expand_tabs = expand_tabs
|
|
121 |
self.replace_whitespace = replace_whitespace
|
|
122 |
self.fix_sentence_endings = fix_sentence_endings
|
|
123 |
self.break_long_words = break_long_words
|
|
124 |
|
|
125 |
|
|
126 |
# -- Private methods -----------------------------------------------
|
|
127 |
# (possibly useful for subclasses to override)
|
|
128 |
|
|
129 |
def _munge_whitespace(self, text):
|
|
130 |
"""_munge_whitespace(text : string) -> string
|
|
131 |
|
|
132 |
Munge whitespace in text: expand tabs and convert all other
|
|
133 |
whitespace characters to spaces. Eg. " foo\tbar\n\nbaz"
|
|
134 |
becomes " foo bar baz".
|
|
135 |
"""
|
|
136 |
if self.expand_tabs:
|
|
137 |
text = string.expandtabs(text)
|
|
138 |
if self.replace_whitespace:
|
|
139 |
if isinstance(text, types.StringType):
|
|
140 |
text = string.translate(text, self.whitespace_trans)
|
|
141 |
elif isinstance(text, types.UnicodeType):
|
|
142 |
# This has to be Python 2.0+ (no unicode before), so
|
|
143 |
# use directly string methods (the string module does not
|
|
144 |
# support translate() with dictionary for unicode).
|
|
145 |
text = text.translate(self.unicode_whitespace_trans)
|
|
146 |
return text
|
|
147 |
|
|
148 |
|
|
149 |
def _split(self, text):
|
|
150 |
"""_split(text : string) -> [string]
|
|
151 |
|
|
152 |
Split the text to wrap into indivisible chunks. Chunks are
|
|
153 |
not quite the same as words; see wrap_chunks() for full
|
|
154 |
details. As an example, the text
|
|
155 |
Look, goof-ball -- use the -b option!
|
|
156 |
breaks into the following chunks:
|
|
157 |
'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ',
|
|
158 |
'use', ' ', 'the', ' ', '-b', ' ', 'option!'
|
|
159 |
"""
|
|
160 |
chunks = self.wordsep_re.split(text)
|
|
161 |
chunks = filter(None, chunks)
|
|
162 |
return chunks
|
|
163 |
|
|
164 |
def _fix_sentence_endings(self, chunks):
|
|
165 |
"""_fix_sentence_endings(chunks : [string])
|
|
166 |
|
|
167 |
Correct for sentence endings buried in 'chunks'. Eg. when the
|
|
168 |
original text contains "... foo.\nBar ...", munge_whitespace()
|
|
169 |
and split() will convert that to [..., "foo.", " ", "Bar", ...]
|
|
170 |
which has one too few spaces; this method simply changes the one
|
|
171 |
space to two.
|
|
172 |
"""
|
|
173 |
i = 0
|
|
174 |
pat = self.sentence_end_re
|
|
175 |
while i < len(chunks)-1:
|
|
176 |
if chunks[i+1] == " " and pat.search(chunks[i]):
|
|
177 |
chunks[i+1] = " "
|
|
178 |
i = i+2
|
|
179 |
else:
|
|
180 |
i = i+1
|
|
181 |
|
|
182 |
def _handle_long_word(self, chunks, cur_line, cur_len, width):
|
|
183 |
"""_handle_long_word(chunks : [string],
|
|
184 |
cur_line : [string],
|
|
185 |
cur_len : int, width : int)
|
|
186 |
|
|
187 |
Handle a chunk of text (most likely a word, not whitespace) that
|
|
188 |
is too long to fit in any line.
|
|
189 |
"""
|
|
190 |
space_left = max(width - cur_len, 1)
|
|
191 |
|
|
192 |
# If we're allowed to break long words, then do so: put as much
|
|
193 |
# of the next chunk onto the current line as will fit.
|
|
194 |
if self.break_long_words:
|
|
195 |
cur_line.append(chunks[0][0:space_left])
|
|
196 |
chunks[0] = chunks[0][space_left:]
|
|
197 |
|
|
198 |
# Otherwise, we have to preserve the long word intact. Only add
|
|
199 |
# it to the current line if there's nothing already there --
|
|
200 |
# that minimizes how much we violate the width constraint.
|
|
201 |
elif not cur_line:
|
|
202 |
cur_line.append(chunks.pop(0))
|
|
203 |
|
|
204 |
# If we're not allowed to break long words, and there's already
|
|
205 |
# text on the current line, do nothing. Next time through the
|
|
206 |
# main loop of _wrap_chunks(), we'll wind up here again, but
|
|
207 |
# cur_len will be zero, so the next line will be entirely
|
|
208 |
# devoted to the long word that we can't handle right now.
|
|
209 |
|
|
210 |
def _wrap_chunks(self, chunks):
|
|
211 |
"""_wrap_chunks(chunks : [string]) -> [string]
|
|
212 |
|
|
213 |
Wrap a sequence of text chunks and return a list of lines of
|
|
214 |
length 'self.width' or less. (If 'break_long_words' is false,
|
|
215 |
some lines may be longer than this.) Chunks correspond roughly
|
|
216 |
to words and the whitespace between them: each chunk is
|
|
217 |
indivisible (modulo 'break_long_words'), but a line break can
|
|
218 |
come between any two chunks. Chunks should not have internal
|
|
219 |
whitespace; ie. a chunk is either all whitespace or a "word".
|
|
220 |
Whitespace chunks will be removed from the beginning and end of
|
|
221 |
lines, but apart from that whitespace is preserved.
|
|
222 |
"""
|
|
223 |
lines = []
|
|
224 |
if self.width <= 0:
|
|
225 |
raise ValueError("invalid width %r (must be > 0)" % self.width)
|
|
226 |
|
|
227 |
while chunks:
|
|
228 |
|
|
229 |
# Start the list of chunks that will make up the current line.
|
|
230 |
# cur_len is just the length of all the chunks in cur_line.
|
|
231 |
cur_line = []
|
|
232 |
cur_len = 0
|
|
233 |
|
|
234 |
# Figure out which static string will prefix this line.
|
|
235 |
if lines:
|
|
236 |
indent = self.subsequent_indent
|
|
237 |
else:
|
|
238 |
indent = self.initial_indent
|
|
239 |
|
|
240 |
# Maximum width for this line.
|
|
241 |
width = self.width - len(indent)
|
|
242 |
|
|
243 |
# First chunk on line is whitespace -- drop it, unless this
|
|
244 |
# is the very beginning of the text (ie. no lines started yet).
|
|
245 |
if string.strip(chunks[0]) == '' and lines:
|
|
246 |
del chunks[0]
|
|
247 |
|
|
248 |
while chunks:
|
|
249 |
l = len(chunks[0])
|
|
250 |
|
|
251 |
# Can at least squeeze this chunk onto the current line.
|
|
252 |
if cur_len + l <= width:
|
|
253 |
cur_line.append(chunks.pop(0))
|
|
254 |
cur_len = cur_len + l
|
|
255 |
|
|
256 |
# Nope, this line is full.
|
|
257 |
else:
|
|
258 |
break
|
|
259 |
|
|
260 |
# The current line is full, and the next chunk is too big to
|
|
261 |
# fit on *any* line (not just this one).
|
|
262 |
if chunks and len(chunks[0]) > width:
|
|
263 |
self._handle_long_word(chunks, cur_line, cur_len, width)
|
|
264 |
|
|
265 |
# If the last chunk on this line is all whitespace, drop it.
|
|
266 |
if cur_line and string.strip(cur_line[-1]) == '':
|
|
267 |
del cur_line[-1]
|
|
268 |
|
|
269 |
# Convert current line back to a string and store it in list
|
|
270 |
# of all lines (return value).
|
|
271 |
if cur_line:
|
|
272 |
lines.append(indent + string.join(cur_line, ''))
|
|
273 |
|
|
274 |
return lines
|
|
275 |
|
|
276 |
|
|
277 |
# -- Public interface ----------------------------------------------
|
|
278 |
|
|
279 |
def wrap(self, text):
|
|
280 |
"""wrap(text : string) -> [string]
|
|
281 |
|
|
282 |
Reformat the single paragraph in 'text' so it fits in lines of
|
|
283 |
no more than 'self.width' columns, and return a list of wrapped
|
|
284 |
lines. Tabs in 'text' are expanded with string.expandtabs(),
|
|
285 |
and all other whitespace characters (including newline) are
|
|
286 |
converted to space.
|
|
287 |
"""
|
|
288 |
text = self._munge_whitespace(text)
|
|
289 |
indent = self.initial_indent
|
|
290 |
chunks = self._split(text)
|
|
291 |
if self.fix_sentence_endings:
|
|
292 |
self._fix_sentence_endings(chunks)
|
|
293 |
return self._wrap_chunks(chunks)
|
|
294 |
|
|
295 |
def fill(self, text):
|
|
296 |
"""fill(text : string) -> string
|
|
297 |
|
|
298 |
Reformat the single paragraph in 'text' to fit in lines of no
|
|
299 |
more than 'self.width' columns, and return a new string
|
|
300 |
containing the entire wrapped paragraph.
|
|
301 |
"""
|
|
302 |
return string.join(self.wrap(text), "\n")
|
|
303 |
|
|
304 |
|
|
305 |
# -- Convenience interface ---------------------------------------------
|
|
306 |
|
|
307 |
def wrap(text, width=70, **kwargs):
|
|
308 |
"""Wrap a single paragraph of text, returning a list of wrapped lines.
|
|
309 |
|
|
310 |
Reformat the single paragraph in 'text' so it fits in lines of no
|
|
311 |
more than 'width' columns, and return a list of wrapped lines. By
|
|
312 |
default, tabs in 'text' are expanded with string.expandtabs(), and
|
|
313 |
all other whitespace characters (including newline) are converted to
|
|
314 |
space. See TextWrapper class for available keyword args to customize
|
|
315 |
wrapping behaviour.
|
|
316 |
"""
|
|
317 |
kwargs["width"] = width
|
|
318 |
w = apply(TextWrapper, (), kwargs)
|
|
319 |
return w.wrap(text)
|
|
320 |
|
|
321 |
def fill(text, width=70, **kwargs):
|
|
322 |
"""Fill a single paragraph of text, returning a new string.
|
|
323 |
|
|
324 |
Reformat the single paragraph in 'text' to fit in lines of no more
|
|
325 |
than 'width' columns, and return a new string containing the entire
|
|
326 |
wrapped paragraph. As with wrap(), tabs are expanded and other
|
|
327 |
whitespace characters converted to space. See TextWrapper class for
|
|
328 |
available keyword args to customize wrapping behaviour.
|
|
329 |
"""
|
|
330 |
kwargs["width"] = width
|
|
331 |
w = apply(TextWrapper, (), kwargs)
|
|
332 |
return w.fill(text)
|
|
333 |
|
|
334 |
|
|
335 |
# -- Loosely related functionality -------------------------------------
|
|
336 |
|
|
337 |
def dedent(text):
|
|
338 |
"""dedent(text : string) -> string
|
|
339 |
|
|
340 |
Remove any whitespace than can be uniformly removed from the left
|
|
341 |
of every line in `text`.
|
|
342 |
|
|
343 |
This can be used e.g. to make triple-quoted strings line up with
|
|
344 |
the left edge of screen/whatever, while still presenting it in the
|
|
345 |
source code in indented form.
|
|
346 |
|
|
347 |
For example:
|
|
348 |
|
|
349 |
def test():
|
|
350 |
# end first line with \ to avoid the empty line!
|
|
351 |
s = '''\
|
|
352 |
hello
|
|
353 |
world
|
|
354 |
'''
|
|
355 |
print repr(s) # prints ' hello\n world\n '
|
|
356 |
print repr(dedent(s)) # prints 'hello\n world\n'
|
|
357 |
"""
|
|
358 |
lines = string.split(string.expandtabs(text), '\n')
|
|
359 |
margin = None
|
|
360 |
for line in lines:
|
|
361 |
content = string.lstrip(line)
|
|
362 |
if not content:
|
|
363 |
continue
|
|
364 |
indent = len(line) - len(content)
|
|
365 |
if margin is None:
|
|
366 |
margin = indent
|
|
367 |
else:
|
|
368 |
margin = min(margin, indent)
|
|
369 |
|
|
370 |
if margin is not None and margin > 0:
|
|
371 |
for i in range(len(lines)):
|
|
372 |
lines[i] = lines[i][margin:]
|
|
373 |
|
|
374 |
return string.join(lines, "\n")
|