diff --git a/Doc/library/textwrap.rst b/Doc/library/textwrap.rst --- a/Doc/library/textwrap.rst +++ b/Doc/library/textwrap.rst @@ -7,17 +7,17 @@ .. sectionauthor:: Greg Ward **Source code:** :source:`Lib/textwrap.py` -------------- 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. .. function:: wrap(text, width=70, **kwargs) Wraps the single paragraph in *text* (a string) so every line is at most *width* characters long. Returns a list of output lines, without final newlines. @@ -43,16 +43,18 @@ to create your own :class:`TextWrapper` 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. +An additional utility function, :func:`indent`, is provided to add +indentation to lines of a string. .. function:: dedent(text) 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. @@ -67,16 +69,35 @@ indentation from strings that have unwan s = '''\ hello world ''' print(repr(s)) # prints ' hello\n world\n ' print(repr(dedent(s))) # prints 'hello\n world\n' +.. function:: indent(text, margin, function=lambda x:x) + + Add *margin* to the beginning of lines in *text*. + + Which lines *margin* are added to is determined by the optional *function* + argument. By default, *margin* is added to all non-empty lines. + + For example:: + + s='hello\n\nworld' + print(repr(s)) # prints 'hello\n\nworld' + print(repr(indent(s,' '))) # prints ' hello\n\n world' + + The optional function argument can be used to make *margin* be added to + empty lines as well:: + + print(repr(indent(s,' ',lambda x:True))) #prints ' hello\n \n world'. + + .. class:: TextWrapper(**kwargs) The :class:`TextWrapper` constructor accepts a number of optional keyword arguments. Each keyword argument corresponds to an instance attribute, so for example :: wrapper = TextWrapper(initial_indent="* ") diff --git a/Lib/test/test_textwrap.py b/Lib/test/test_textwrap.py --- a/Lib/test/test_textwrap.py +++ b/Lib/test/test_textwrap.py @@ -6,17 +6,17 @@ # Currently maintained by Greg Ward. # # $Id$ # 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): '''Parent class with utility methods for textwrap tests.''' def show(self, textin): if isinstance(textin, list): result = [] @@ -581,16 +581,101 @@ def foo(): text = " \t hello there\n \t how are you?" self.assertEqual(expect, dedent(text)) text = " \thello there\n \t how are you?" expect = "hello there\n how are you?" self.assertEqual(expect, dedent(text)) +# Test textwrap.indent +class IndentFunctionTestCase(unittest.TestCase): + + def test_indent_nomargin(self): + # indent should do nothing if 'margin' is empty. + text = "Hi.\nThis is a test.\nTesting." + self.assertEqual(text, indent(text, '')) + + # Similar, with a blank line. + text = "Hi.\nThis is a test.\n\nTesting." + self.assertEqual(text, indent(text, '')) + + # Similar, with leading and trailing blank lines. + text = "\nHi.\nThis is a test.\nTesting.\n" + self.assertEqual(text, indent(text, '')) + + def test_indent_nomargin_function(self): + # The same as test_indent_nomargin, but using the optional + # function argument + function = lambda x: True + text = "Hi.\nThis is a test.\nTesting." + self.assertEqual(text, indent(text, '', function)) + + # Similar, with a blank line. + text = "Hi.\nThis is a test.\n\nTesting." + self.assertEqual(text, indent(text, '', function)) + + # Similar, with leading and trailing blank lines. + text = "\nHi.\nThis is a test.\nTesting.\n" + self.assertEqual(text, indent(text, '', function)) + + def test_indent_no_function(self): + # Testing basic functionality, with no function argument. + text = "Hi.\nThis is a test.\nTesting." + expect = " Hi.\n This is a test.\n Testing." + self.assertEqual(expect, indent(text, 4 * ' ')) + + # Similar, with a blank line. + text = "Hi.\nThis is a test.\n\nTesting." + expect = " Hi.\n This is a test.\n\n Testing." + self.assertEqual(expect, indent(text, 4 * ' ')) + + # Similar, with leading and trailing blank lines. + text = "\nHi.\nThis is a test.\nTesting.\n" + expect = "\n Hi.\n This is a test.\n Testing.\n" + self.assertEqual(expect, indent(text, 4 * ' ')) + + def test_indent_function1(self): + # Testing indent with the following function, which adds 'margin' + # to all lines, including empty ones. + function = lambda x: True + text = "Hi.\nThis is a test.\nTesting." + expect = " Hi.\n This is a test.\n Testing." + self.assertEqual(expect, indent(text, 4 * ' ', function)) + + # Similar, with a blank line. + text = "Hi.\nThis is a test.\n\nTesting." + expect = " Hi.\n This is a test.\n \n Testing." + self.assertEqual(expect, indent(text, 4 * ' ', function)) + + # Similar, with leading and trailing blank lines. + text = "\nHi.\nThis is a test.\nTesting.\n" + expect = " \n Hi.\n This is a test.\n Testing.\n " + self.assertEqual(expect, indent(text, 4 * ' ', function)) + + def test_indent_function2(self): + # Testing indent with the following function, which adds 'margin' + # to only empty lines. + function = lambda x: not x + text = "Hi.\nThis is a test.\nTesting." + expect = "Hi.\nThis is a test.\nTesting." + self.assertEqual(expect, indent(text, 4 * ' ', function)) + + # Similar, with a blank line. + text = "Hi.\nThis is a test.\n\nTesting." + expect = "Hi.\nThis is a test.\n \nTesting." + self.assertEqual(expect, indent(text, 4 * ' ', function)) + + # Similar, with leading and trailing blank lines. + text = "\nHi.\nThis is a test.\nTesting.\n" + expect = " \nHi.\nThis is a test.\nTesting.\n " + self.assertEqual(expect, indent(text, 4 * ' ', function)) + + def test_main(): support.run_unittest(WrapTestCase, LongWordTestCase, IndentTestCases, - DedentTestCase) + DedentTestCase, + IndentFunctionTestCase) if __name__ == '__main__': test_main() diff --git a/Lib/textwrap.py b/Lib/textwrap.py --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -2,17 +2,17 @@ """ # Copyright (C) 1999-2001 Gregory P. Ward. # Copyright (C) 2002, 2003 Python Software Foundation. # Written by Greg Ward 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 # ISO-8859-1, 0xa0 is non-breaking whitespace, so in certain locales # that character winds up in string.whitespace. Respecting # string.whitespace in those cases would 1) make textwrap treat 0xa0 the # same as any other whitespace char, which is clearly wrong (it's a # *non-breaking* space), 2) possibly cause problems with Unicode, @@ -376,12 +376,27 @@ def dedent(text): 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, margin, function=lambda x:x): + """Adds 'margin' to the beginning of every line in 'text'. + + If 'function' is provided, 'margin' will only be added to the lines + where 'function(line)' is True. If 'function' is not provided, + it will default to adding 'margin' to all non-empty lines. + """ + + #Add an extra newline to the end of text. This ensures that any + #existing trailing newline isn't lost. + text+="\n" + + return '\n'.join((margin + x if function(x) else x) \ + for x in text.splitlines()) + if __name__ == "__main__": #print dedent("\tfoo\n\tbar") #print dedent(" \thello there\n \t how are you?") print(dedent("Hello there.\n This is indented."))