Skip to content

Commit 4fae8cd

Browse files
committed
Close #13857: Added textwrap.indent() function (initial patch by Ezra
Berch)
1 parent 3c4acd8 commit 4fae8cd

6 files changed

Lines changed: 201 additions & 7 deletions

File tree

β€ŽDoc/library/textwrap.rstβ€Ž

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
The :mod:`textwrap` module provides two convenience functions, :func:`wrap` and
1414
:func:`fill`, as well as :class:`TextWrapper`, the class that does all the work,
15-
and a utility function :func:`dedent`. If you're just wrapping or filling one
15+
and two utility functions, :func:`dedent` and :func:`indent`. If you're just wrapping or filling one
1616
or two text strings, the convenience functions should be good enough;
1717
otherwise, you should use an instance of :class:`TextWrapper` for efficiency.
1818

@@ -45,9 +45,10 @@ Text is preferably wrapped on whitespaces and right after the hyphens in
4545
hyphenated words; only then will long words be broken if necessary, unless
4646
:attr:`TextWrapper.break_long_words` is set to false.
4747

48-
An additional utility function, :func:`dedent`, is provided to remove
49-
indentation from strings that have unwanted whitespace to the left of the text.
50-
48+
Two additional utility function, :func:`dedent` and :func:`indent`, are
49+
provided to remove indentation from strings that have unwanted whitespace
50+
to the left of the text and to add an arbitrary prefix to selected lines
51+
in a block of text.
5152

5253
.. function:: dedent(text)
5354

@@ -72,6 +73,32 @@ indentation from strings that have unwanted whitespace to the left of the text.
7273
print(repr(dedent(s))) # prints 'hello\n world\n'
7374

7475

76+
.. function:: indent(text, prefix, predicate=None)
77+
78+
Add *prefix* to the beginning of selected lines in *text*.
79+
80+
Lines are separated by calling ``text.splitlines(True)``.
81+
82+
By default, *prefix* is added to all lines that do not consist
83+
solely of whitespace (including any line endings).
84+
85+
For example::
86+
87+
>>> s = 'hello\n\n \nworld'
88+
>>> indent(s, ' ')
89+
' hello\n\n \n world'
90+
91+
The optional *predicate* argument can be used to control which lines
92+
are indented. For example, it is easy to add *prefix* to even empty
93+
and whitespace-only lines::
94+
95+
>>> print(indent(s, '+ ', lambda line: True))
96+
+ hello
97+
+
98+
+
99+
+ world
100+
101+
75102
.. class:: TextWrapper(**kwargs)
76103

77104
The :class:`TextWrapper` constructor accepts a number of optional keyword

β€ŽDoc/whatsnew/3.3.rstβ€Ž

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,6 +1406,14 @@ sys
14061406

14071407
(:issue:`11223`)
14081408

1409+
textwrap
1410+
--------
1411+
1412+
* The :mod:`textwrap` module has a new :func:`~textwrap.indent` that makes
1413+
it straightforward to add a common prefix to selected lines in a block
1414+
of text.
1415+
1416+
(:issue:`13857`)
14091417

14101418
time
14111419
----

β€ŽLib/test/test_textwrap.pyβ€Ž

Lines changed: 138 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import unittest
1212
from test import support
1313

14-
from textwrap import TextWrapper, wrap, fill, dedent
14+
from textwrap import TextWrapper, wrap, fill, dedent, indent
1515

1616

1717
class BaseTestCase(unittest.TestCase):
@@ -594,11 +594,147 @@ def test_dedent_preserve_margin_tabs(self):
594594
self.assertEqual(expect, dedent(text))
595595

596596

597+
# Test textwrap.indent
598+
class IndentTestCase(unittest.TestCase):
599+
# The examples used for tests. If any of these change, the expected
600+
# results in the various test cases must also be updated.
601+
# The roundtrip cases are separate, because textwrap.dedent doesn't
602+
# handle Windows line endings
603+
ROUNDTRIP_CASES = (
604+
# Basic test case
605+
"Hi.\nThis is a test.\nTesting.",
606+
# Include a blank line
607+
"Hi.\nThis is a test.\n\nTesting.",
608+
# Include leading and trailing blank lines
609+
"\nHi.\nThis is a test.\nTesting.\n",
610+
)
611+
CASES = ROUNDTRIP_CASES + (
612+
# Use Windows line endings
613+
"Hi.\r\nThis is a test.\r\nTesting.\r\n",
614+
# Pathological case
615+
"\nHi.\r\nThis is a test.\n\r\nTesting.\r\n\n",
616+
)
617+
618+
def test_indent_nomargin_default(self):
619+
# indent should do nothing if 'prefix' is empty.
620+
for text in self.CASES:
621+
self.assertEqual(indent(text, ''), text)
622+
623+
def test_indent_nomargin_explicit_default(self):
624+
# The same as test_indent_nomargin, but explicitly requesting
625+
# the default behaviour by passing None as the predicate
626+
for text in self.CASES:
627+
self.assertEqual(indent(text, '', None), text)
628+
629+
def test_indent_nomargin_all_lines(self):
630+
# The same as test_indent_nomargin, but using the optional
631+
# predicate argument
632+
predicate = lambda line: True
633+
for text in self.CASES:
634+
self.assertEqual(indent(text, '', predicate), text)
635+
636+
def test_indent_no_lines(self):
637+
# Explicitly skip indenting any lines
638+
predicate = lambda line: False
639+
for text in self.CASES:
640+
self.assertEqual(indent(text, ' ', predicate), text)
641+
642+
def test_roundtrip_spaces(self):
643+
# A whitespace prefix should roundtrip with dedent
644+
for text in self.ROUNDTRIP_CASES:
645+
self.assertEqual(dedent(indent(text, ' ')), text)
646+
647+
def test_roundtrip_tabs(self):
648+
# A whitespace prefix should roundtrip with dedent
649+
for text in self.ROUNDTRIP_CASES:
650+
self.assertEqual(dedent(indent(text, '\t\t')), text)
651+
652+
def test_roundtrip_mixed(self):
653+
# A whitespace prefix should roundtrip with dedent
654+
for text in self.ROUNDTRIP_CASES:
655+
self.assertEqual(dedent(indent(text, ' \t \t ')), text)
656+
657+
def test_indent_default(self):
658+
# Test default indenting of lines that are not whitespace only
659+
prefix = ' '
660+
expected = (
661+
# Basic test case
662+
" Hi.\n This is a test.\n Testing.",
663+
# Include a blank line
664+
" Hi.\n This is a test.\n\n Testing.",
665+
# Include leading and trailing blank lines
666+
"\n Hi.\n This is a test.\n Testing.\n",
667+
# Use Windows line endings
668+
" Hi.\r\n This is a test.\r\n Testing.\r\n",
669+
# Pathological case
670+
"\n Hi.\r\n This is a test.\n\r\n Testing.\r\n\n",
671+
)
672+
for text, expect in zip(self.CASES, expected):
673+
self.assertEqual(indent(text, prefix), expect)
674+
675+
def test_indent_explicit_default(self):
676+
# Test default indenting of lines that are not whitespace only
677+
prefix = ' '
678+
expected = (
679+
# Basic test case
680+
" Hi.\n This is a test.\n Testing.",
681+
# Include a blank line
682+
" Hi.\n This is a test.\n\n Testing.",
683+
# Include leading and trailing blank lines
684+
"\n Hi.\n This is a test.\n Testing.\n",
685+
# Use Windows line endings
686+
" Hi.\r\n This is a test.\r\n Testing.\r\n",
687+
# Pathological case
688+
"\n Hi.\r\n This is a test.\n\r\n Testing.\r\n\n",
689+
)
690+
for text, expect in zip(self.CASES, expected):
691+
self.assertEqual(indent(text, prefix, None), expect)
692+
693+
def test_indent_all_lines(self):
694+
# Add 'prefix' to all lines, including whitespace-only ones.
695+
prefix = ' '
696+
expected = (
697+
# Basic test case
698+
" Hi.\n This is a test.\n Testing.",
699+
# Include a blank line
700+
" Hi.\n This is a test.\n \n Testing.",
701+
# Include leading and trailing blank lines
702+
" \n Hi.\n This is a test.\n Testing.\n",
703+
# Use Windows line endings
704+
" Hi.\r\n This is a test.\r\n Testing.\r\n",
705+
# Pathological case
706+
" \n Hi.\r\n This is a test.\n \r\n Testing.\r\n \n",
707+
)
708+
predicate = lambda line: True
709+
for text, expect in zip(self.CASES, expected):
710+
self.assertEqual(indent(text, prefix, predicate), expect)
711+
712+
def test_indent_empty_lines(self):
713+
# Add 'prefix' solely to whitespace-only lines.
714+
prefix = ' '
715+
expected = (
716+
# Basic test case
717+
"Hi.\nThis is a test.\nTesting.",
718+
# Include a blank line
719+
"Hi.\nThis is a test.\n \nTesting.",
720+
# Include leading and trailing blank lines
721+
" \nHi.\nThis is a test.\nTesting.\n",
722+
# Use Windows line endings
723+
"Hi.\r\nThis is a test.\r\nTesting.\r\n",
724+
# Pathological case
725+
" \nHi.\r\nThis is a test.\n \r\nTesting.\r\n \n",
726+
)
727+
predicate = lambda line: not line.strip()
728+
for text, expect in zip(self.CASES, expected):
729+
self.assertEqual(indent(text, prefix, predicate), expect)
730+
731+
597732
def test_main():
598733
support.run_unittest(WrapTestCase,
599734
LongWordTestCase,
600735
IndentTestCases,
601-
DedentTestCase)
736+
DedentTestCase,
737+
IndentTestCase)
602738

603739
if __name__ == '__main__':
604740
test_main()

β€ŽLib/textwrap.pyβ€Ž

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import re
99

10-
__all__ = ['TextWrapper', 'wrap', 'fill', 'dedent']
10+
__all__ = ['TextWrapper', 'wrap', 'fill', 'dedent', 'indent']
1111

1212
# Hardcode the recognized whitespace characters to the US-ASCII
1313
# whitespace characters. The main reason for doing this is that in
@@ -386,6 +386,25 @@ def dedent(text):
386386
text = re.sub(r'(?m)^' + margin, '', text)
387387
return text
388388

389+
390+
def indent(text, prefix, predicate=None):
391+
"""Adds 'prefix' to the beginning of selected lines in 'text'.
392+
393+
If 'predicate' is provided, 'prefix' will only be added to the lines
394+
where 'predicate(line)' is True. If 'predicate' is not provided,
395+
it will default to adding 'prefix' to all non-empty lines that do not
396+
consist solely of whitespace characters.
397+
"""
398+
if predicate is None:
399+
def predicate(line):
400+
return line.strip()
401+
402+
def prefixed_lines():
403+
for line in text.splitlines(True):
404+
yield (prefix + line if predicate(line) else line)
405+
return ''.join(prefixed_lines())
406+
407+
389408
if __name__ == "__main__":
390409
#print dedent("\tfoo\n\tbar")
391410
#print dedent(" \thello there\n \t how are you?")

β€ŽMisc/ACKSβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Alexander β€œΠ‘Π°ΡˆΠ°β€ Belopolsky
8282
Eli Bendersky
8383
Andrew Bennetts
8484
Andy Bensky
85+
Ezra Berch
8586
Michel Van den Bergh
8687
Julian Berman
8788
Brice Berna

β€ŽMisc/NEWSβ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ Core and Builtins
2121
Library
2222
-------
2323

24+
- Issue #13857: Added textwrap.indent() function (initial patch by Ezra
25+
Berch)
26+
2427
- Issue #2736: Added datetime.timestamp() method.
2528

2629
- Issue #13854: Make multiprocessing properly handle non-integer

0 commit comments

Comments
 (0)