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.