text wrapping and filling c 19992001 gregory p ward c 2002 2003 python software foundation written by greg ward gwardpython net hardcode the recognized whitespace characters to the usascii whitespace characters the main reason for doing this is that some unicode spaces like u00a0 are nonbreaking whitespaces object for wrappingfilling text the public interface consists of the wrap and fill methods the other methods are just there for subclasses to override in order to tweak the default behaviour if you want to completely replace the main wrapping algorithm you ll probably have to override wrapchunks several instance attributes control various aspects of wrapping width default 70 the maximum width of wrapped lines unless breaklongwords is false initialindent default string that will be prepended to the first line of wrapped output counts towards the line s width subsequentindent default string that will be prepended to all lines save the first of wrapped output also counts towards each line s width expandtabs default true expand tabs in input text to spaces before further processing each tab will become 0 tabsize spaces depending on its position in its line if false each tab is treated as a single character tabsize default 8 expand tabs in input text to 0 tabsize spaces unless expandtabs is false replacewhitespace default true replace all whitespace characters in the input text by spaces after tab expansion note that if expandtabs is false and replacewhitespace is true every tab will be converted to a single space fixsentenceendings default false ensure that sentenceending punctuation is always followed by two spaces off by default because the algorithm is unavoidably imperfect breaklongwords default true break words longer than width if false those words will not be broken and some lines might be longer than width breakonhyphens default true allow breaking hyphenated words if true wrapping will occur preferably on whitespaces and right after hyphens part of compound words dropwhitespace default true drop leading and trailing whitespace from lines maxlines default none truncate wrapped lines placeholder default append to the last line of truncated text this funky little regex is just the trick for splitting text up into wordwrappable chunks e g hello there you goofball use the b option splits into hello there you goofball use the b option after stripping out empty strings hyphenated word this less funky little regex just split on recognized spaces e g hello there you goofball use the b option splits into hello there you goofball use the b option xxx this is not locale or charsetaware string lowercase is usascii only and therefore englishonly private methods possibly useful for subclasses to override mungewhitespacetext string string munge whitespace in text expand tabs and convert all other whitespace characters to spaces eg footbarnnbaz becomes foo bar baz splittext string string split the text to wrap into indivisible chunks chunks are not quite the same as words see wrapchunks for full details as an example the text look goofball use the b option breaks into the following chunks look goof ball use the b option if breakonhyphens is true or in look goofball use the b option otherwise fixsentenceendingschunks string correct for sentence endings buried in chunks eg when the original text contains foo nbar mungewhitespace and split will convert that to foo bar which has one too few spaces this method simply changes the one space to two handlelongwordchunks string curline string curlen int width int handle a chunk of text most likely a word not whitespace that is too long to fit in any line figure out when indent is larger than the specified width and make sure at least one character is stripped off on every pass if we re allowed to break long words then do so put as much of the next chunk onto the current line as will fit break after last hyphen but only if there are nonhyphens before it otherwise we have to preserve the long word intact only add it to the current line if there s nothing already there that minimizes how much we violate the width constraint if we re not allowed to break long words and there s already text on the current line do nothing next time through the main loop of wrapchunks we ll wind up here again but curlen will be zero so the next line will be entirely devoted to the long word that we can t handle right now wrapchunkschunks string string wrap a sequence of text chunks and return a list of lines of length self width or less if breaklongwords is false some lines may be longer than this chunks correspond roughly to words and the whitespace between them each chunk is indivisible modulo breaklongwords but a line break can come between any two chunks chunks should not have internal whitespace ie a chunk is either all whitespace or a word whitespace chunks will be removed from the beginning and end of lines but apart from that whitespace is preserved arrange in reverse order so items can be efficiently popped from a stack of chucks start the list of chunks that will make up the current line curlen is just the length of all the chunks in curline figure out which static string will prefix this line maximum width for this line first chunk on line is whitespace drop it unless this is the very beginning of the text ie no lines started yet can at least squeeze this chunk onto the current line nope this line is full the current line is full and the next chunk is too big to fit on any line not just this one if the last chunk on this line is all whitespace drop it convert current line back to a string and store it in list of all lines return value public interface wraptext string string reformat the single paragraph in text so it fits in lines of no more than self width columns and return a list of wrapped lines tabs in text are expanded with string expandtabs and all other whitespace characters including newline are converted to space filltext string string reformat the single paragraph in text to fit in lines of no more than self width columns and return a new string containing the entire wrapped paragraph convenience interface wrap a single paragraph of text returning a list of wrapped lines reformat the single paragraph in text so it fits in lines of no more than width columns and return a list of wrapped lines by default tabs in text are expanded with string expandtabs and all other whitespace characters including newline are converted to space see textwrapper class for available keyword args to customize wrapping behaviour fill a single paragraph of text returning a new string reformat the single paragraph in text to fit in lines of no more than width columns and return a new string containing the entire wrapped paragraph as with wrap tabs are expanded and other whitespace characters converted to space see textwrapper class for available keyword args to customize wrapping behaviour collapse and truncate the given text to fit in the given width the text first has its whitespace collapsed if it then fits in the width it is returned as is otherwise as many words as possible are joined and then the placeholder is appended textwrap shortenhello world width12 hello world textwrap shortenhello world width11 hello loosely related functionality remove any common leading whitespace from every line in text this can be used to make triplequoted strings line up with the left edge of the display while still presenting them in the source code in indented form note that tabs and spaces are both treated as whitespace but they are not equal the lines hello and thello are considered to have no common leading whitespace entirely blank lines are normalized to a newline character look for the longest leading string of spaces and tabs common to all lines current line more deeply indented than previous winner no change previous winner is still on top current line consistent with and no deeper than previous winner it s the new winner find the largest common whitespace between current line and previous winner sanity check testingdebugging only adds prefix to the beginning of selected lines in text if predicate is provided prefix will only be added to the lines where predicateline is true if predicate is not provided it will default to adding prefix to all nonempty lines that do not consist solely of whitespace characters str splitlinestrue doesn t produce empty string splitlinestrue foon splitlinestrue foon so we can use just not s isspace here print dedenttfoontbar print dedent thello theren t how are you c 1999 2001 gregory p ward c 2002 2003 python software foundation written by greg ward gward python net hardcode the recognized whitespace characters to the us ascii whitespace characters the main reason for doing this is that some unicode spaces like u00a0 are non breaking whitespaces object for wrapping filling text the public interface consists of the wrap and fill methods the other methods are just there for subclasses to override in order to tweak the default behaviour if you want to completely replace the main wrapping algorithm you ll probably have to override _wrap_chunks several instance attributes control various aspects of wrapping width default 70 the maximum width of wrapped lines unless break_long_words is false initial_indent default string that will be prepended to the first line of wrapped output counts towards the line s width subsequent_indent default string that will be prepended to all lines save the first of wrapped output also counts towards each line s width expand_tabs default true expand tabs in input text to spaces before further processing each tab will become 0 tabsize spaces depending on its position in its line if false each tab is treated as a single character tabsize default 8 expand tabs in input text to 0 tabsize spaces unless expand_tabs is false replace_whitespace default true replace all whitespace characters in the input text by spaces after tab expansion note that if expand_tabs is false and replace_whitespace is true every tab will be converted to a single space fix_sentence_endings default false ensure that sentence ending punctuation is always followed by two spaces off by default because the algorithm is unavoidably imperfect break_long_words default true break words longer than width if false those words will not be broken and some lines might be longer than width break_on_hyphens default true allow breaking hyphenated words if true wrapping will occur preferably on whitespaces and right after hyphens part of 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 this funky little regex is just the trick for splitting text up into word wrappable chunks e g hello there you goof ball use the b option splits into hello there you goof ball use the b option after stripping out empty strings any whitespace ws s em dash between words wp s 2 w word possibly hyphenated nws s hyphenated word lt s 2 lt s lt s lt s lt s end of word ws s z em dash wp s 2 w this less funky little regex just split on recognized spaces e g hello there you goof ball use the b option splits into hello there you goof ball use the b option xxx this is not locale or charset aware string lowercase is us ascii only and therefore english only lowercase letter sentence ending punct optional end of quote end of chunk private methods possibly useful for subclasses to override _munge_whitespace text string string munge whitespace in text expand tabs and convert all other whitespace characters to spaces eg foo tbar n nbaz becomes foo bar baz _split text string string split the text to wrap into indivisible chunks chunks are not quite the same as words see _wrap_chunks for full details as an example the text look goof ball use the b option breaks into the following chunks look goof ball use the b option if break_on_hyphens is true or in look goof ball use the b option otherwise _fix_sentence_endings chunks string correct for sentence endings buried in chunks eg when the original text contains foo nbar munge_whitespace and split will convert that to foo bar which has one too few spaces this method simply changes the one space to two _handle_long_word chunks string cur_line string cur_len int width int handle a chunk of text most likely a word not whitespace that is too long to fit in any line figure out when indent is larger than the specified width and make sure at least one character is stripped off on every pass if we re allowed to break long words then do so put as much of the next chunk onto the current line as will fit break after last hyphen but only if there are non hyphens before it otherwise we have to preserve the long word intact only add it to the current line if there s nothing already there that minimizes how much we violate the width constraint if we re not allowed to break long words and there s already text on the current line do nothing next time through the main loop of _wrap_chunks we ll wind up here again but cur_len will be zero so the next line will be entirely devoted to the long word that we can t handle right now _wrap_chunks chunks string string wrap a sequence of text chunks and return a list of lines of length self width or less if break_long_words is false some lines may be longer than this chunks correspond roughly to words and the whitespace between them each chunk is indivisible modulo break_long_words but a line break can come between any two chunks chunks should not have internal whitespace ie a chunk is either all whitespace or a word whitespace chunks will be removed from the beginning and end of lines but apart from that whitespace is preserved arrange in reverse order so items can be efficiently popped from a stack of chucks start the list of chunks that will make up the current line cur_len is just the length of all the chunks in cur_line figure out which static string will prefix this line maximum width for this line first chunk on line is whitespace drop it unless this is the very beginning of the text ie no lines started yet can at least squeeze this chunk onto the current line nope this line is full the current line is full and the next chunk is too big to fit on any line not just this one if the last chunk on this line is all whitespace drop it convert current line back to a string and store it in list of all lines return value public interface wrap text string string reformat the single paragraph in text so it fits in lines of no more than self width columns and return a list of wrapped lines tabs in text are expanded with string expandtabs and all other whitespace characters including newline are converted to space fill text string string reformat the single paragraph in text to fit in lines of no more than self width columns and return a new string containing the entire wrapped paragraph convenience interface wrap a single paragraph of text returning a list of wrapped lines reformat the single paragraph in text so it fits in lines of no more than width columns and return a list of wrapped lines by default tabs in text are expanded with string expandtabs and all other whitespace characters including newline are converted to space see textwrapper class for available keyword args to customize wrapping behaviour fill a single paragraph of text returning a new string reformat the single paragraph in text to fit in lines of no more than width columns and return a new string containing the entire wrapped paragraph as with wrap tabs are expanded and other whitespace characters converted to space see textwrapper class for available keyword args to customize wrapping behaviour collapse and truncate the given text to fit in the given width the text first has its whitespace collapsed if it then fits in the width it is returned as is otherwise as many words as possible are joined and then the placeholder is appended textwrap shorten hello world width 12 hello world textwrap shorten hello world width 11 hello loosely related functionality remove any common leading whitespace from every line in text this can be used to make triple quoted strings line up with the left edge of the display while still presenting them in the source code in indented form note that tabs and spaces are both treated as whitespace but they are not equal the lines hello and thello are considered to have no common leading whitespace entirely blank lines are normalized to a newline character look for the longest leading string of spaces and tabs common to all lines current line more deeply indented than previous winner no change previous winner is still on top current line consistent with and no deeper than previous winner it s the new winner find the largest common whitespace between current line and previous winner sanity check testing debugging only adds prefix to the beginning of selected lines in text if predicate is provided prefix will only be added to the lines where predicate line is true if predicate is not provided it will default to adding prefix to all non empty lines that do not consist solely of whitespace characters str splitlines true doesn t produce empty string splitlines true foo n splitlines true foo n so we can use just not s isspace here print dedent tfoo n tbar print dedent thello there n t how are you
import re

__all__ = ['TextWrapper', 'wrap', 'fill', 'dedent', 'indent', 'shorten']




_whitespace = '\t\n\x0b\x0c\r '

class TextWrapper:
    

    unicode_whitespace_trans = dict.fromkeys(map(ord, _whitespace), ord(' '))

    
    
    
    
    
    
    word_punct = r'[\w!"\'&.,?]'
    letter = r'[^\d\W]'
    whitespace = r'[%s]' % re.escape(_whitespace)
    nowhitespace = '[^' + whitespace[1:]
    wordsep_re = re.compile(r % {'wp': word_punct, 'lt': letter,
                'ws': whitespace, 'nws': nowhitespace},
        re.VERBOSE)
    del word_punct, letter, nowhitespace

    
    
    
    
    wordsep_simple_re = re.compile(r'(%s+)' % whitespace)
    del whitespace

    
    
    sentence_end_re = re.compile(r'[a-z]'             
                                 r'[\.\!\?]'          
                                 r'[\"\']?'           
                                 r'\Z')               

    def __init__(self,
                 width=70,
                 initial_indent="",
                 subsequent_indent="",
                 expand_tabs=True,
                 replace_whitespace=True,
                 fix_sentence_endings=False,
                 break_long_words=True,
                 drop_whitespace=True,
                 break_on_hyphens=True,
                 tabsize=8,
                 *,
                 max_lines=None,
                 placeholder=' [...]'):
        self.width = width
        self.initial_indent = initial_indent
        self.subsequent_indent = subsequent_indent
        self.expand_tabs = expand_tabs
        self.replace_whitespace = replace_whitespace
        self.fix_sentence_endings = fix_sentence_endings
        self.break_long_words = break_long_words
        self.drop_whitespace = drop_whitespace
        self.break_on_hyphens = break_on_hyphens
        self.tabsize = tabsize
        self.max_lines = max_lines
        self.placeholder = placeholder


    
    

    def _munge_whitespace(self, text):
        
        if self.expand_tabs:
            text = text.expandtabs(self.tabsize)
        if self.replace_whitespace:
            text = text.translate(self.unicode_whitespace_trans)
        return text


    def _split(self, text):
        
        if self.break_on_hyphens is True:
            chunks = self.wordsep_re.split(text)
        else:
            chunks = self.wordsep_simple_re.split(text)
        chunks = [c for c in chunks if c]
        return chunks

    def _fix_sentence_endings(self, chunks):
        
        i = 0
        patsearch = self.sentence_end_re.search
        while i < len(chunks)-1:
            if chunks[i+1] == " " and patsearch(chunks[i]):
                chunks[i+1] = "  "
                i += 2
            else:
                i += 1

    def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
        
        
        
        if width < 1:
            space_left = 1
        else:
            space_left = width - cur_len

        
        
        if self.break_long_words:
            end = space_left
            chunk = reversed_chunks[-1]
            if self.break_on_hyphens and len(chunk) > space_left:
                
                
                hyphen = chunk.rfind('-', 0, space_left)
                if hyphen > 0 and any(c != '-' for c in chunk[:hyphen]):
                    end = hyphen + 1
            cur_line.append(chunk[:end])
            reversed_chunks[-1] = chunk[end:]

        
        
        
        elif not cur_line:
            cur_line.append(reversed_chunks.pop())

        
        
        
        
        

    def _wrap_chunks(self, chunks):
        
        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")

        
        
        chunks.reverse()

        while chunks:

            
            
            cur_line = []
            cur_len = 0

            
            if lines:
                indent = self.subsequent_indent
            else:
                indent = self.initial_indent

            
            width = self.width - len(indent)

            
            
            if self.drop_whitespace and chunks[-1].strip() == '' and lines:
                del chunks[-1]

            while chunks:
                l = len(chunks[-1])

                
                if cur_len + l <= width:
                    cur_line.append(chunks.pop())
                    cur_len += l

                
                else:
                    break

            
            
            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 self.drop_whitespace and cur_line and cur_line[-1].strip() == '':
                cur_len -= len(cur_line[-1])
                del cur_line[-1]

            if 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):
                    
                    
                    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

    def _split_chunks(self, text):
        text = self._munge_whitespace(text)
        return self._split(text)

    

    def wrap(self, text):
        
        chunks = self._split_chunks(text)
        if self.fix_sentence_endings:
            self._fix_sentence_endings(chunks)
        return self._wrap_chunks(chunks)

    def fill(self, text):
        
        return "\n".join(self.wrap(text))




def wrap(text, width=70, **kwargs):
    
    w = TextWrapper(width=width, **kwargs)
    return w.wrap(text)

def fill(text, width=70, **kwargs):
    
    w = TextWrapper(width=width, **kwargs)
    return w.fill(text)

def shorten(text, width, **kwargs):
    
    w = TextWrapper(width=width, max_lines=1, **kwargs)
    return w.fill(' '.join(text.strip().split()))




_whitespace_only_re = re.compile('^[ \t]+$', re.MULTILINE)
_leading_whitespace_re = re.compile('(^[ \t]*)(?:[^ \t\n])', re.MULTILINE)

def dedent(text):
    
    
    
    margin = None
    text = _whitespace_only_re.sub('', text)
    indents = _leading_whitespace_re.findall(text)
    for indent in indents:
        if margin is None:
            margin = indent

        
        
        elif indent.startswith(margin):
            pass

        
        
        elif margin.startswith(indent):
            margin = indent

        
        
        else:
            for i, (x, y) in enumerate(zip(margin, indent)):
                if x != y:
                    margin = margin[:i]
                    break

    
    if 0 and margin:
        for line in text.split("\n"):
            assert not line or line.startswith(margin), \
                   "line = %r, margin = %r" % (line, margin)

    if margin:
        text = re.sub(r'(?m)^' + margin, '', text)
    return text


def indent(text, prefix, predicate=None):
    
    if predicate is None:
        
        
        
        
        predicate = lambda s: not s.isspace()

    prefixed_lines = []
    for line in text.splitlines(True):
        if predicate(line):
            prefixed_lines.append(prefix)
        prefixed_lines.append(line)

    return ''.join(prefixed_lines)


if __name__ == "__main__":
    
    
    print(dedent("Hello there.\n  This is indented."))