Python textwrap – Wrap your text to terminal size

Following the last post “Python textwrap – Fix Your Multiline Strings Indentations” – where we learned how to fix the indentation caused by the python indentation standards.
In this post I want to show you how to create a wrap to your text that support multiline and smart indent.

This might sound old to you but the original terminal (standard) was and still is 80 characters width. Long ago when I’ve started working on CLI applications and scripts my boss asked me to see the “usage/help” of one of them, then he looked at me and said – This is not 80 characters width! This is not terminal standard – Fix this!”
Back then I’ve just trimmed my lined and fix this.
More then 500 scripts later and 4 languages into the future I’ve looked at this and thought – There might be a better way to do this.
And there in python was the answer.

Let’s start with example:

>>> import textwrap
>>> x = "This is the first line of the text - This line is far more then then 80 characters and will need to be wraped to 80 characters width"
>>> print x
This is the first line of the text - This line is far more then then 80 characters and will need to be wraped to 80 characters width
>>> #Lets wrap it to 80 characters
>>> print textwrap.fill(x, width=80)
This is the first line of the text - This line is far more then then 80
characters and will need to be wraped to 80 characters width
SyntaxError: invalid syntax
>>> #See that the words won't break (Unless this is a long word)

Now let’s try that with a multiline string:

>>> x = '''
This is a mltiline string, now this is the first line and it will be more then 50 characters width for the example.
Now we create a new line with more then 50 width as the second line'''
>>> print textwrap.fill(x, width=50)
 This is a mltiline string, now this is the first
line and it will be more then 50 characters width
for the example. Now we create a new line with
more then 50 width as the second line
>>>

As you can see the second line was integrated into the first line instead of being on it’s own line – This is one of the problems with textwrap – it works only on a single line.

The simple solution is this:
Split the text to separated lines and do the wrap/fill for every line separately and then combine them back together.
Here is a small example:

import textwrap
import re
 
text = '''
            Some text
            This is not an indent text
                Now this is an indent text
            Some very long line with some letters more then you will actually need because we want to text text wrap on the correct place. This should be the third line of this text before the indentation and for the wrap.
                Now this is a new line - we want to wrap this line to 80 characters but we want to save the indentation instead of using it like the normal wrap who will use to shift to the start of hte line
'''
 
 
text = textwrap.dedent(text)
text = text[text.find('\n')+1:text.rfind('\n')]
print '# Original text'
print '#-----------------------'
print text
print ''
print '# After textwrap.fill'
print '#-----------------------'
print textwrap.fill(text, width=80)
print ''
 
textLines = text.split('\n')
wrapedLines = []
# Preserve any indent (after the general indent)
_indentRe = re.compile('^(\W+)')
for line in textLines:
    preservedIndent = ''
    existIndent = re.search(_indentRe, line)
    # Change the existing wrap indent to the original one
    if (existIndent):
        preservedIndent = existIndent.groups()[0]
    wrapedLines.append(textwrap.fill(line, width=80, subsequent_indent=preservedIndent))
 
text = '\n'.join(wrapedLines)
print '# After line by line fill'
print '#-----------------------'
print text

You can see in this code that I’ve not only split the multiline string to a list of one line strings and run the textwrap.fill on every single one of them but I’ve also added a little piece of code that preserves an existing indentation also in the wrapped text.
The result:

# Original text
#-----------------------
Some text
This is not an indent text
    Now this is an indent text
Some very long line with some letters more then you will actually need because we want to text text wrap on the correct place. This should be the third line of this text before the indentation and for the wrap.
    Now this is a new line - we want to wrap this line to 80 characters but we want to save the indentation instead of using it like the normal wrap who will use to shift to the start of hte line
 
# After textwrap.fill
#-----------------------
Some text This is not an indent text     Now this is an indent text Some very
long line with some letters more then you will actually need because we want to
text text wrap on the correct place. This should be the third line of this text
before the indentation and for the wrap.     Now this is a new line - we want to
wrap this line to 80 characters but we want to save the indentation instead of
using it like the normal wrap who will use to shift to the start of hte line
 
# After line by line fill
#-----------------------
Some text
This is not an indent text
    Now this is an indent text
Some very long line with some letters more then you will actually need because
we want to text text wrap on the correct place. This should be the third line of
this text before the indentation and for the wrap.
    Now this is a new line - we want to wrap this line to 80 characters but we
    want to save the indentation instead of using it like the normal wrap who
    will use to shift to the start of hte line

So as you can see in the text above the wrapping occurred line by line and the indentation inside the general one was also preserved.

I’ve found a shorter version but without the preserved indentation in here, here is the change:

print '\n'.join(line.strip() for line in re.findall(r'.{1,40}(?:\s+|$)', text))

The result for this line is:

Some text
This is not an indent text
Now this is an indent text
Some very long line with some letters
more then you will actually need because
we want to text text wrap on the correct
place. This should be the third line of
this text before the indentation and for
the wrap.
Now this is a new line - we want to wrap
this line to 80 characters but we want
to save the indentation instead of using
it like the normal wrap who will use to
shift to the start of hte line

As you can see the inner indentation wasn’t preserved.

In a future post I’ll create a new textwrap module with these changes as options. I’m hoping in the future that I will be able to insert these changes into the original package.

 

Leave a Reply

Your email address will not be published. Required fields are marked *