Close #13857: Added textwrap.indent() function (initial patch by Ezra
Berch)
This commit is contained in:
parent
3c4acd8bf9
commit
4fae8cdaea
|
@ -12,7 +12,7 @@
|
|||
|
||||
The :mod:`textwrap` module provides two convenience functions, :func:`wrap` and
|
||||
:func:`fill`, as well as :class:`TextWrapper`, the class that does all the work,
|
||||
and a utility function :func:`dedent`. If you're just wrapping or filling one
|
||||
and two utility functions, :func:`dedent` and :func:`indent`. If you're just wrapping or filling one
|
||||
or two text strings, the convenience functions should be good enough;
|
||||
otherwise, you should use an instance of :class:`TextWrapper` for efficiency.
|
||||
|
||||
|
@ -45,9 +45,10 @@ Text is preferably wrapped on whitespaces and right after the hyphens in
|
|||
hyphenated words; only then will long words be broken if necessary, unless
|
||||
:attr:`TextWrapper.break_long_words` is set to false.
|
||||
|
||||
An additional utility function, :func:`dedent`, is provided to remove
|
||||
indentation from strings that have unwanted whitespace to the left of the text.
|
||||
|
||||
Two additional utility function, :func:`dedent` and :func:`indent`, are
|
||||
provided to remove indentation from strings that have unwanted whitespace
|
||||
to the left of the text and to add an arbitrary prefix to selected lines
|
||||
in a block of text.
|
||||
|
||||
.. function:: dedent(text)
|
||||
|
||||
|
@ -72,6 +73,32 @@ indentation from strings that have unwanted whitespace to the left of the text.
|
|||
print(repr(dedent(s))) # prints 'hello\n world\n'
|
||||
|
||||
|
||||
.. function:: indent(text, prefix, predicate=None)
|
||||
|
||||
Add *prefix* to the beginning of selected lines in *text*.
|
||||
|
||||
Lines are separated by calling ``text.splitlines(True)``.
|
||||
|
||||
By default, *prefix* is added to all lines that do not consist
|
||||
solely of whitespace (including any line endings).
|
||||
|
||||
For example::
|
||||
|
||||
>>> s = 'hello\n\n \nworld'
|
||||
>>> indent(s, ' ')
|
||||
' hello\n\n \n world'
|
||||
|
||||
The optional *predicate* argument can be used to control which lines
|
||||
are indented. For example, it is easy to add *prefix* to even empty
|
||||
and whitespace-only lines::
|
||||
|
||||
>>> print(indent(s, '+ ', lambda line: True))
|
||||
+ hello
|
||||
+
|
||||
+
|
||||
+ world
|
||||
|
||||
|
||||
.. class:: TextWrapper(**kwargs)
|
||||
|
||||
The :class:`TextWrapper` constructor accepts a number of optional keyword
|
||||
|
|
|
@ -1406,6 +1406,14 @@ sys
|
|||
|
||||
(:issue:`11223`)
|
||||
|
||||
textwrap
|
||||
--------
|
||||
|
||||
* The :mod:`textwrap` module has a new :func:`~textwrap.indent` that makes
|
||||
it straightforward to add a common prefix to selected lines in a block
|
||||
of text.
|
||||
|
||||
(:issue:`13857`)
|
||||
|
||||
time
|
||||
----
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
import unittest
|
||||
from test import support
|
||||
|
||||
from textwrap import TextWrapper, wrap, fill, dedent
|
||||
from textwrap import TextWrapper, wrap, fill, dedent, indent
|
||||
|
||||
|
||||
class BaseTestCase(unittest.TestCase):
|
||||
|
@ -594,11 +594,147 @@ def foo():
|
|||
self.assertEqual(expect, dedent(text))
|
||||
|
||||
|
||||
# Test textwrap.indent
|
||||
class IndentTestCase(unittest.TestCase):
|
||||
# The examples used for tests. If any of these change, the expected
|
||||
# results in the various test cases must also be updated.
|
||||
# The roundtrip cases are separate, because textwrap.dedent doesn't
|
||||
# handle Windows line endings
|
||||
ROUNDTRIP_CASES = (
|
||||
# Basic test case
|
||||
"Hi.\nThis is a test.\nTesting.",
|
||||
# Include a blank line
|
||||
"Hi.\nThis is a test.\n\nTesting.",
|
||||
# Include leading and trailing blank lines
|
||||
"\nHi.\nThis is a test.\nTesting.\n",
|
||||
)
|
||||
CASES = ROUNDTRIP_CASES + (
|
||||
# Use Windows line endings
|
||||
"Hi.\r\nThis is a test.\r\nTesting.\r\n",
|
||||
# Pathological case
|
||||
"\nHi.\r\nThis is a test.\n\r\nTesting.\r\n\n",
|
||||
)
|
||||
|
||||
def test_indent_nomargin_default(self):
|
||||
# indent should do nothing if 'prefix' is empty.
|
||||
for text in self.CASES:
|
||||
self.assertEqual(indent(text, ''), text)
|
||||
|
||||
def test_indent_nomargin_explicit_default(self):
|
||||
# The same as test_indent_nomargin, but explicitly requesting
|
||||
# the default behaviour by passing None as the predicate
|
||||
for text in self.CASES:
|
||||
self.assertEqual(indent(text, '', None), text)
|
||||
|
||||
def test_indent_nomargin_all_lines(self):
|
||||
# The same as test_indent_nomargin, but using the optional
|
||||
# predicate argument
|
||||
predicate = lambda line: True
|
||||
for text in self.CASES:
|
||||
self.assertEqual(indent(text, '', predicate), text)
|
||||
|
||||
def test_indent_no_lines(self):
|
||||
# Explicitly skip indenting any lines
|
||||
predicate = lambda line: False
|
||||
for text in self.CASES:
|
||||
self.assertEqual(indent(text, ' ', predicate), text)
|
||||
|
||||
def test_roundtrip_spaces(self):
|
||||
# A whitespace prefix should roundtrip with dedent
|
||||
for text in self.ROUNDTRIP_CASES:
|
||||
self.assertEqual(dedent(indent(text, ' ')), text)
|
||||
|
||||
def test_roundtrip_tabs(self):
|
||||
# A whitespace prefix should roundtrip with dedent
|
||||
for text in self.ROUNDTRIP_CASES:
|
||||
self.assertEqual(dedent(indent(text, '\t\t')), text)
|
||||
|
||||
def test_roundtrip_mixed(self):
|
||||
# A whitespace prefix should roundtrip with dedent
|
||||
for text in self.ROUNDTRIP_CASES:
|
||||
self.assertEqual(dedent(indent(text, ' \t \t ')), text)
|
||||
|
||||
def test_indent_default(self):
|
||||
# Test default indenting of lines that are not whitespace only
|
||||
prefix = ' '
|
||||
expected = (
|
||||
# Basic test case
|
||||
" Hi.\n This is a test.\n Testing.",
|
||||
# Include a blank line
|
||||
" Hi.\n This is a test.\n\n Testing.",
|
||||
# Include leading and trailing blank lines
|
||||
"\n Hi.\n This is a test.\n Testing.\n",
|
||||
# Use Windows line endings
|
||||
" Hi.\r\n This is a test.\r\n Testing.\r\n",
|
||||
# Pathological case
|
||||
"\n Hi.\r\n This is a test.\n\r\n Testing.\r\n\n",
|
||||
)
|
||||
for text, expect in zip(self.CASES, expected):
|
||||
self.assertEqual(indent(text, prefix), expect)
|
||||
|
||||
def test_indent_explicit_default(self):
|
||||
# Test default indenting of lines that are not whitespace only
|
||||
prefix = ' '
|
||||
expected = (
|
||||
# Basic test case
|
||||
" Hi.\n This is a test.\n Testing.",
|
||||
# Include a blank line
|
||||
" Hi.\n This is a test.\n\n Testing.",
|
||||
# Include leading and trailing blank lines
|
||||
"\n Hi.\n This is a test.\n Testing.\n",
|
||||
# Use Windows line endings
|
||||
" Hi.\r\n This is a test.\r\n Testing.\r\n",
|
||||
# Pathological case
|
||||
"\n Hi.\r\n This is a test.\n\r\n Testing.\r\n\n",
|
||||
)
|
||||
for text, expect in zip(self.CASES, expected):
|
||||
self.assertEqual(indent(text, prefix, None), expect)
|
||||
|
||||
def test_indent_all_lines(self):
|
||||
# Add 'prefix' to all lines, including whitespace-only ones.
|
||||
prefix = ' '
|
||||
expected = (
|
||||
# Basic test case
|
||||
" Hi.\n This is a test.\n Testing.",
|
||||
# Include a blank line
|
||||
" Hi.\n This is a test.\n \n Testing.",
|
||||
# Include leading and trailing blank lines
|
||||
" \n Hi.\n This is a test.\n Testing.\n",
|
||||
# Use Windows line endings
|
||||
" Hi.\r\n This is a test.\r\n Testing.\r\n",
|
||||
# Pathological case
|
||||
" \n Hi.\r\n This is a test.\n \r\n Testing.\r\n \n",
|
||||
)
|
||||
predicate = lambda line: True
|
||||
for text, expect in zip(self.CASES, expected):
|
||||
self.assertEqual(indent(text, prefix, predicate), expect)
|
||||
|
||||
def test_indent_empty_lines(self):
|
||||
# Add 'prefix' solely to whitespace-only lines.
|
||||
prefix = ' '
|
||||
expected = (
|
||||
# Basic test case
|
||||
"Hi.\nThis is a test.\nTesting.",
|
||||
# Include a blank line
|
||||
"Hi.\nThis is a test.\n \nTesting.",
|
||||
# Include leading and trailing blank lines
|
||||
" \nHi.\nThis is a test.\nTesting.\n",
|
||||
# Use Windows line endings
|
||||
"Hi.\r\nThis is a test.\r\nTesting.\r\n",
|
||||
# Pathological case
|
||||
" \nHi.\r\nThis is a test.\n \r\nTesting.\r\n \n",
|
||||
)
|
||||
predicate = lambda line: not line.strip()
|
||||
for text, expect in zip(self.CASES, expected):
|
||||
self.assertEqual(indent(text, prefix, predicate), expect)
|
||||
|
||||
|
||||
def test_main():
|
||||
support.run_unittest(WrapTestCase,
|
||||
LongWordTestCase,
|
||||
IndentTestCases,
|
||||
DedentTestCase)
|
||||
DedentTestCase,
|
||||
IndentTestCase)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_main()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import re
|
||||
|
||||
__all__ = ['TextWrapper', 'wrap', 'fill', 'dedent']
|
||||
__all__ = ['TextWrapper', 'wrap', 'fill', 'dedent', 'indent']
|
||||
|
||||
# Hardcode the recognized whitespace characters to the US-ASCII
|
||||
# whitespace characters. The main reason for doing this is that in
|
||||
|
@ -386,6 +386,25 @@ def dedent(text):
|
|||
text = re.sub(r'(?m)^' + margin, '', text)
|
||||
return text
|
||||
|
||||
|
||||
def indent(text, prefix, predicate=None):
|
||||
"""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.
|
||||
"""
|
||||
if predicate is None:
|
||||
def predicate(line):
|
||||
return line.strip()
|
||||
|
||||
def prefixed_lines():
|
||||
for line in text.splitlines(True):
|
||||
yield (prefix + line if predicate(line) else line)
|
||||
return ''.join(prefixed_lines())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#print dedent("\tfoo\n\tbar")
|
||||
#print dedent(" \thello there\n \t how are you?")
|
||||
|
|
|
@ -82,6 +82,7 @@ Alexander “Саша” Belopolsky
|
|||
Eli Bendersky
|
||||
Andrew Bennetts
|
||||
Andy Bensky
|
||||
Ezra Berch
|
||||
Michel Van den Bergh
|
||||
Julian Berman
|
||||
Brice Berna
|
||||
|
|
Loading…
Reference in New Issue