diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2013-10-15 18:22:54 (GMT) |
---|---|---|
committer | Serhiy Storchaka <storchaka@gmail.com> | 2013-10-15 18:22:54 (GMT) |
commit | acc9f3fb1622c0685b52097b977cafada742be99 (patch) | |
tree | 6f75fc531b1731d6467637d1ffe3f71e39e09f62 /Lib/textwrap.py | |
parent | bc2bfa6b68e138986e57e2925e5a31c0f17615ab (diff) | |
download | cpython-acc9f3fb1622c0685b52097b977cafada742be99.zip cpython-acc9f3fb1622c0685b52097b977cafada742be99.tar.gz cpython-acc9f3fb1622c0685b52097b977cafada742be99.tar.bz2 |
Issue #18725: The textwrap module now supports truncating multiline text.
Diffstat (limited to 'Lib/textwrap.py')
-rw-r--r-- | Lib/textwrap.py | 88 |
1 files changed, 49 insertions, 39 deletions
diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 27ebc16..15a7534 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -19,8 +19,6 @@ __all__ = ['TextWrapper', 'wrap', 'fill', 'dedent', 'indent'] # since 0xa0 is not in range(128). _whitespace = '\t\n\x0b\x0c\r ' -_default_placeholder = ' [...]' - class TextWrapper: """ Object for wrapping/filling text. The public interface consists of @@ -64,6 +62,10 @@ class TextWrapper: compound words. drop_whitespace (default: true) Drop leading and trailing whitespace from lines. + max_lines (default: None) + Truncate wrapped lines. + placeholder (default: ' [...]') + Append to the last line of truncated text. """ unicode_whitespace_trans = {} @@ -106,7 +108,10 @@ class TextWrapper: break_long_words=True, drop_whitespace=True, break_on_hyphens=True, - tabsize=8): + tabsize=8, + *, + max_lines=None, + placeholder=' [...]'): self.width = width self.initial_indent = initial_indent self.subsequent_indent = subsequent_indent @@ -117,6 +122,8 @@ class TextWrapper: self.drop_whitespace = drop_whitespace self.break_on_hyphens = break_on_hyphens self.tabsize = tabsize + self.max_lines = max_lines + self.placeholder = placeholder # -- Private methods ----------------------------------------------- @@ -225,6 +232,13 @@ class TextWrapper: lines = [] if self.width <= 0: raise ValueError("invalid width %r (must be > 0)" % self.width) + if self.max_lines is not None: + if self.max_lines > 1: + indent = self.subsequent_indent + else: + indent = self.initial_indent + if len(indent) + len(self.placeholder.lstrip()) > self.width: + raise ValueError("placeholder too large for max width") # Arrange in reverse order so items can be efficiently popped # from a stack of chucks. @@ -267,15 +281,41 @@ class TextWrapper: # fit on *any* line (not just this one). if chunks and len(chunks[-1]) > width: self._handle_long_word(chunks, cur_line, cur_len, width) + cur_len = sum(map(len, cur_line)) # If the last chunk on this line is all whitespace, drop it. if self.drop_whitespace and cur_line and cur_line[-1].strip() == '': + cur_len -= len(cur_line[-1]) del cur_line[-1] - # Convert current line back to a string and store it in list - # of all lines (return value). if cur_line: - lines.append(indent + ''.join(cur_line)) + if (self.max_lines is None or + len(lines) + 1 < self.max_lines or + (not chunks or + self.drop_whitespace and + len(chunks) == 1 and + not chunks[0].strip()) and cur_len <= width): + # Convert current line back to a string and store it in + # list of all lines (return value). + lines.append(indent + ''.join(cur_line)) + else: + while cur_line: + if (cur_line[-1].strip() and + cur_len + len(self.placeholder) <= width): + cur_line.append(self.placeholder) + lines.append(indent + ''.join(cur_line)) + break + cur_len -= len(cur_line[-1]) + del cur_line[-1] + else: + if lines: + prev_line = lines[-1].rstrip() + if (len(prev_line) + len(self.placeholder) <= + self.width): + lines[-1] = prev_line + self.placeholder + break + lines.append(indent + self.placeholder.lstrip()) + break return lines @@ -308,36 +348,6 @@ class TextWrapper: """ return "\n".join(self.wrap(text)) - def shorten(self, text, *, placeholder=_default_placeholder): - """shorten(text: str) -> str - - Collapse and truncate the given text to fit in 'self.width' columns. - """ - max_length = self.width - if max_length < len(placeholder.strip()): - raise ValueError("placeholder too large for max width") - sep = ' ' - sep_len = len(sep) - parts = [] - cur_len = 0 - chunks = self._split_chunks(text) - for chunk in chunks: - if not chunk.strip(): - continue - chunk_len = len(chunk) + sep_len if parts else len(chunk) - if cur_len + chunk_len > max_length: - break - parts.append(chunk) - cur_len += chunk_len - else: - # No truncation necessary - return sep.join(parts) - max_truncated_length = max_length - len(placeholder) - while parts and cur_len > max_truncated_length: - last = parts.pop() - cur_len -= len(last) + sep_len - return (sep.join(parts) + placeholder).strip() - # -- Convenience interface --------------------------------------------- @@ -366,7 +376,7 @@ def fill(text, width=70, **kwargs): w = TextWrapper(width=width, **kwargs) return w.fill(text) -def shorten(text, width, *, placeholder=_default_placeholder, **kwargs): +def shorten(text, width, **kwargs): """Collapse and truncate the given text to fit in the given width. The text first has its whitespace collapsed. If it then fits in @@ -378,8 +388,8 @@ def shorten(text, width, *, placeholder=_default_placeholder, **kwargs): >>> textwrap.shorten("Hello world!", width=11) 'Hello [...]' """ - w = TextWrapper(width=width, **kwargs) - return w.shorten(text, placeholder=placeholder) + w = TextWrapper(width=width, max_lines=1, **kwargs) + return w.fill(' '.join(text.strip().split())) # -- Loosely related functionality ------------------------------------- |