textwrap.dedent replaces tabs?

Tom Plunket tomas at fancy.org
Sat Dec 23 14:10:33 EST 2006


Frederic Rentsch wrote:

> Following a call to dedent () it shouldn't be hard to translate leading 
> groups of so many spaces back to tabs.

Sure, but the point is more that I don't think it's valid to change to
tabs in the first place.

E.g.:

 input = ' ' + '\t' + 'hello\n' +
         '\t' + 'world'

 output = textwrap.dedent(input)

will yield all of the leading whitespace stripped, which IMHO is a
violation of its stated function.  In this case, nothing should be
stripped, because the leading whitespace in these two lines does not
/actually/ match.  Sure, it visually matches, but that's not the point
(although I can understand that that's a point of contention in the
interpreter anyway, I would have no problem with it not accepting "1 tab
= 8 spaces" for indentation...  But that's another holy war.

> If I understand your problem, you want to restore the dedented line to 
> its original composition if spaces and tabs are mixed and this doesn't 
> work because the information doesn't survive dedent ().

Sure, although would there be a case to be made to simply not strip the
tabs in the first place?

Like this, keeping current functionality and everything...  (although I
would think if someone wanted tabs expanded, they'd call expandtabs on
the input before calling the function!):

def dedent(text, expand_tabs=True):
    """dedent(text : string, expand_tabs : bool) -> string

    Remove any whitespace than can be uniformly removed from the left
    of every line in `text`, optionally expanding tabs before altering
    the text.

    This can be used e.g. to make triple-quoted strings line up with
    the left edge of screen/whatever, while still presenting it in the
    source code in indented form.

    For example:

        def test():
            # end first line with \ to avoid the empty line!
            s = '''\
             hello
            \t  world
            '''
            print repr(s)     # prints '     hello\n    \t  world\n    '
            print repr(dedent(s))  # prints ' hello\n\t  world\n'
    """
    if expand_tabs:
        text = text.expandtabs()
    lines = text.split('\n')
    
    margin = None
    for line in lines:
        if margin is None:
            content = line.lstrip()
            if not content:
                continue
            indent = len(line) - len(content)
            margin = line[:indent]
        elif not line.startswith(margin):
            if len(line) < len(margin):
                content = line.lstrip()
                if not content:
                    continue
            while not line.startswith(margin):
                margin = margin[:-1]

    if margin is not None and len(margin) > 0:
        margin = len(margin)
        for i in range(len(lines)):
            lines[i] = lines[i][margin:]

    return '\n'.join(lines)

import unittest

class DedentTest(unittest.TestCase):
    def testBasicWithSpaces(self):
        input = "\n   Hello\n      World"
        expected = "\nHello\n   World"
        self.failUnlessEqual(expected, dedent(input))

    def testBasicWithTabLeadersSpacesInside(self):
        input = "\n\tHello\n\t   World"
        expected = "\nHello\n   World"
        self.failUnlessEqual(expected, dedent(input, False))
        
    def testAllTabs(self):
        input = "\t\tHello\n\tWorld"
        expected = "\tHello\nWorld"
        self.failUnlessEqual(expected, dedent(input, False))
        
    def testFirstLineNotIndented(self):
        input = "Hello\n\tWorld"
        expected = input
        self.failUnlessEqual(expected, dedent(input, False))
        
    def testMixedTabsAndSpaces(self):
        input = "  \t Hello\n   \tWorld"
        expected = "\t Hello\n \tWorld"
        self.failUnlessEqual(expected, dedent(input, False))
        
if __name__ == '__main__':
    unittest.main()
-tom!

-- 



More information about the Python-list mailing list