1919# since 0xa0 is not in range(128).
2020_whitespace = '\t \n \x0b \x0c \r '
2121
22- _default_placeholder = ' [...]'
23-
2422class TextWrapper :
2523 """
2624 Object for wrapping/filling text. The public interface consists of
@@ -64,6 +62,10 @@ class TextWrapper:
6462 compound words.
6563 drop_whitespace (default: true)
6664 Drop leading and trailing whitespace from lines.
65+ max_lines (default: None)
66+ Truncate wrapped lines.
67+ placeholder (default: ' [...]')
68+ Append to the last line of truncated text.
6769 """
6870
6971 unicode_whitespace_trans = {}
@@ -106,7 +108,10 @@ def __init__(self,
106108 break_long_words = True ,
107109 drop_whitespace = True ,
108110 break_on_hyphens = True ,
109- tabsize = 8 ):
111+ tabsize = 8 ,
112+ * ,
113+ max_lines = None ,
114+ placeholder = ' [...]' ):
110115 self .width = width
111116 self .initial_indent = initial_indent
112117 self .subsequent_indent = subsequent_indent
@@ -117,6 +122,8 @@ def __init__(self,
117122 self .drop_whitespace = drop_whitespace
118123 self .break_on_hyphens = break_on_hyphens
119124 self .tabsize = tabsize
125+ self .max_lines = max_lines
126+ self .placeholder = placeholder
120127
121128
122129 # -- Private methods -----------------------------------------------
@@ -225,6 +232,13 @@ def _wrap_chunks(self, chunks):
225232 lines = []
226233 if self .width <= 0 :
227234 raise ValueError ("invalid width %r (must be > 0)" % self .width )
235+ if self .max_lines is not None :
236+ if self .max_lines > 1 :
237+ indent = self .subsequent_indent
238+ else :
239+ indent = self .initial_indent
240+ if len (indent ) + len (self .placeholder .lstrip ()) > self .width :
241+ raise ValueError ("placeholder too large for max width" )
228242
229243 # Arrange in reverse order so items can be efficiently popped
230244 # from a stack of chucks.
@@ -267,15 +281,41 @@ def _wrap_chunks(self, chunks):
267281 # fit on *any* line (not just this one).
268282 if chunks and len (chunks [- 1 ]) > width :
269283 self ._handle_long_word (chunks , cur_line , cur_len , width )
284+ cur_len = sum (map (len , cur_line ))
270285
271286 # If the last chunk on this line is all whitespace, drop it.
272287 if self .drop_whitespace and cur_line and cur_line [- 1 ].strip () == '' :
288+ cur_len -= len (cur_line [- 1 ])
273289 del cur_line [- 1 ]
274290
275- # Convert current line back to a string and store it in list
276- # of all lines (return value).
277291 if cur_line :
278- lines .append (indent + '' .join (cur_line ))
292+ if (self .max_lines is None or
293+ len (lines ) + 1 < self .max_lines or
294+ (not chunks or
295+ self .drop_whitespace and
296+ len (chunks ) == 1 and
297+ not chunks [0 ].strip ()) and cur_len <= width ):
298+ # Convert current line back to a string and store it in
299+ # list of all lines (return value).
300+ lines .append (indent + '' .join (cur_line ))
301+ else :
302+ while cur_line :
303+ if (cur_line [- 1 ].strip () and
304+ cur_len + len (self .placeholder ) <= width ):
305+ cur_line .append (self .placeholder )
306+ lines .append (indent + '' .join (cur_line ))
307+ break
308+ cur_len -= len (cur_line [- 1 ])
309+ del cur_line [- 1 ]
310+ else :
311+ if lines :
312+ prev_line = lines [- 1 ].rstrip ()
313+ if (len (prev_line ) + len (self .placeholder ) <=
314+ self .width ):
315+ lines [- 1 ] = prev_line + self .placeholder
316+ break
317+ lines .append (indent + self .placeholder .lstrip ())
318+ break
279319
280320 return lines
281321
@@ -308,36 +348,6 @@ def fill(self, text):
308348 """
309349 return "\n " .join (self .wrap (text ))
310350
311- def shorten (self , text , * , placeholder = _default_placeholder ):
312- """shorten(text: str) -> str
313-
314- Collapse and truncate the given text to fit in 'self.width' columns.
315- """
316- max_length = self .width
317- if max_length < len (placeholder .strip ()):
318- raise ValueError ("placeholder too large for max width" )
319- sep = ' '
320- sep_len = len (sep )
321- parts = []
322- cur_len = 0
323- chunks = self ._split_chunks (text )
324- for chunk in chunks :
325- if not chunk .strip ():
326- continue
327- chunk_len = len (chunk ) + sep_len if parts else len (chunk )
328- if cur_len + chunk_len > max_length :
329- break
330- parts .append (chunk )
331- cur_len += chunk_len
332- else :
333- # No truncation necessary
334- return sep .join (parts )
335- max_truncated_length = max_length - len (placeholder )
336- while parts and cur_len > max_truncated_length :
337- last = parts .pop ()
338- cur_len -= len (last ) + sep_len
339- return (sep .join (parts ) + placeholder ).strip ()
340-
341351
342352# -- Convenience interface ---------------------------------------------
343353
@@ -366,7 +376,7 @@ def fill(text, width=70, **kwargs):
366376 w = TextWrapper (width = width , ** kwargs )
367377 return w .fill (text )
368378
369- def shorten (text , width , * , placeholder = _default_placeholder , * *kwargs ):
379+ def shorten (text , width , ** kwargs ):
370380 """Collapse and truncate the given text to fit in the given width.
371381
372382 The text first has its whitespace collapsed. If it then fits in
@@ -378,8 +388,8 @@ def shorten(text, width, *, placeholder=_default_placeholder, **kwargs):
378388 >>> textwrap.shorten("Hello world!", width=11)
379389 'Hello [...]'
380390 """
381- w = TextWrapper (width = width , ** kwargs )
382- return w .shorten ( text , placeholder = placeholder )
391+ w = TextWrapper (width = width , max_lines = 1 , ** kwargs )
392+ return w .fill ( ' ' . join ( text . strip (). split ()) )
383393
384394
385395# -- Loosely related functionality -------------------------------------
0 commit comments