Given this template:


In [16]:
template = """\
Dynamically expanded example:

    @EXAMPLE@

End of file.
"""

Replace @EXAMPLE@ with the following text, preserving indentation:


In [17]:
expansion = """\
def hello():
    print('hellowording..')
hello()
"""

So, the result should look like:


In [18]:
test = """\
Dynamically expanded example:

    def hello():
        print('hellowording..')
    hello()

End of file.
"""

Standard string replacement (FAIL)


In [21]:
try1 = template.replace('@EXAMPLE@', expansion)
print(try1)
try1 == test


Dynamically expanded example:

    def hello():
    print('hellowording..')
hello()


End of file.

Out[21]:
False

Using string.Template (FAIL)


In [24]:
from string import Template
Template.delimiter = '@'

In [33]:
converter = Template(template)
converter


Out[33]:
<string.Template at 0x33c4130>

In [32]:
try2 = converter.substitute(EXAMPLE=expansion)
print(try2)


Dynamically expanded example:

    @EXAMPLE@

End of file.

Subclasssing


In [35]:
class Converter(Template):
    delimiter = '@'
converter2 = Converter(template)
converter2


Out[35]:
<__main__.Converter at 0x33c4390>

In [36]:
converter2.substitute(EXAMPLE=expansion)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-36-5878bbe91641> in <module>()
----> 1 converter2.substitute(EXAMPLE=expansion)

C:\Python27\lib\string.pyc in substitute(*args, **kws)
    174             raise ValueError('Unrecognized named group in pattern',
    175                              self.pattern)
--> 176         return self.pattern.sub(convert, self.template)
    177 
    178     def safe_substitute(*args, **kws):

C:\Python27\lib\string.pyc in convert(mo)
    171                 return self.delimiter
    172             if mo.group('invalid') is not None:
--> 173                 self._invalid(mo)
    174             raise ValueError('Unrecognized named group in pattern',
    175                              self.pattern)

C:\Python27\lib\string.pyc in _invalid(self, mo)
    144             lineno = len(lines)
    145         raise ValueError('Invalid placeholder in string: line %d, col %d' %
--> 146                          (lineno, colno))
    147 
    148     def substitute(*args, **kws):

ValueError: Invalid placeholder in string: line 3, col 13

In [37]:
Converter("Try with single @EXAMPLE delimiter.").substitute(EXAMPLE='at')


Out[37]:
'Try with single at delimiter.'

In [38]:
Converter("Try with double @EXAMPLE@ delimiter.").substitute(EXAMPLE='at')


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-38-5a460037c4cd> in <module>()
----> 1 Converter("Try with double @EXAMPLE@ delimiter.").substitute(EXAMPLE='at')

C:\Python27\lib\string.pyc in substitute(*args, **kws)
    174             raise ValueError('Unrecognized named group in pattern',
    175                              self.pattern)
--> 176         return self.pattern.sub(convert, self.template)
    177 
    178     def safe_substitute(*args, **kws):

C:\Python27\lib\string.pyc in convert(mo)
    171                 return self.delimiter
    172             if mo.group('invalid') is not None:
--> 173                 self._invalid(mo)
    174             raise ValueError('Unrecognized named group in pattern',
    175                              self.pattern)

C:\Python27\lib\string.pyc in _invalid(self, mo)
    144             lineno = len(lines)
    145         raise ValueError('Invalid placeholder in string: line %d, col %d' %
--> 146                          (lineno, colno))
    147 
    148     def substitute(*args, **kws):

ValueError: Invalid placeholder in string: line 1, col 25

In [50]:
class DoubleEnded(Template):
    delimiter = '@'
    pattern = r'''
    %(delim)s             # start with @
    (?:                   # non-capturing group
        (?P<escaped>%(delim)s) |       # if next is @, both are escaped @
        (?P<named>%(id)s%(delim)s) |    # if next is id, it should end with @
        (?P<braced>%(id)s%(delim)s) |   # need to have braced in regex too
        (?P<invalid>)                  # catch all for errors
    )
    '''
print DoubleEnded.pattern


<_sre.SRE_Pattern object at 0x02640488>

In [51]:
DoubleEnded("Try with double @EXAMPLE@ delimiter.").substitute(EXAMPLE='at')


Out[51]:
'Try with double @EXAMPLE@ delimiter.'

^^^ This doesn't work, because if pattern is defined, delimiter won't work anymore.


In [57]:
class DoubleEndedAt(Template):
    pattern = r'''
    %(delim)s             # start with @
    (?:                   # non-capturing group
        (?P<escaped>%(delim)s) |       # if next is @, both are escaped @
        (?P<named>%(id)s)%(delim)s |    # if next is id, it should end with @
        (?P<braced>%(id)s)%(delim)s |   # need to have braced in regex too
        (?P<invalid>)                  # catch all for errors
    )
    ''' % dict(delim='@', id='[_a-z][_a-z0-9]*')

In [58]:
DoubleEndedAt("Try with double @EXAMPLE@ delimiter.").substitute(EXAMPLE='at')


Out[58]:
'Try with double at delimiter.'

In [59]:
DoubleEndedAt("Try with single @EXAMPLE delimiter.").substitute(EXAMPLE='at')


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-59-0427b214a63c> in <module>()
----> 1 DoubleEndedAt("Try with single @EXAMPLE delimiter.").substitute(EXAMPLE='at')

C:\Python27\lib\string.pyc in substitute(*args, **kws)
    174             if mo.group('invalid') is not None:
    175                 self._invalid(mo)
--> 176             raise ValueError('Unrecognized named group in pattern',
    177                              self.pattern)
    178         return self.pattern.sub(convert, self.template)

C:\Python27\lib\string.pyc in convert(mo)
    171                 return '%s' % (val,)
    172             if mo.group('escaped') is not None:
--> 173                 return self.delimiter
    174             if mo.group('invalid') is not None:
    175                 self._invalid(mo)

C:\Python27\lib\string.pyc in _invalid(self, mo)
    144         else:
    145             colno = i - len(''.join(lines[:-1]))
--> 146             lineno = len(lines)
    147         raise ValueError('Invalid placeholder in string: line %d, col %d' %
    148                          (lineno, colno))

ValueError: Invalid placeholder in string: line 1, col 17

In [60]:
DoubleEndedAt("Try with escaped @@ delimiter.").substitute(EXAMPLE='at')


Out[60]:
'Try with escaped @ delimiter.'

Finally, the real example.


In [64]:
try3 = DoubleEndedAt(template).substitute(EXAMPLE=expansion)
print(try3)
try3 == test


Dynamically expanded example:

    def hello():
    print('hellowording..')
hello()


End of file.

Out[64]:
False

Custom function (SUCCESS)


In [106]:
def expand(text, needle, replacement):
    """replace `needle` with replacement preserving indentation"""
    output = []
    repl = replacement.splitlines(True)
    for i, line in enumerate(text.splitlines(True)):
        pos = line.find(needle)
        #print i, repr(pos)
        if pos == -1:
            output.append(line)
        else:
            # add first line of replacement
            outline = [line[0:pos], repl[0]]
            if len(replacement) == 1:
                outline.append(line[pos+len(needle):])
            else:
                # [ ] copy whitespace symbols
                indent = ' '*pos
                for rep in repl[1:]:
                    outline.append(indent)
                    outline.append(rep)
                outline.append(line[pos+len(needle):])
            output.append(''.join(outline))
    return ''.join(output)

In [107]:
print expand(template, '@EXAMPLE@', expansion)


Dynamically expanded example:

    def hello():
        print('hellowording..')
    hello()


End of file.


In [111]:
try4 = expand(template, '@EXAMPLE@', expansion.strip())
print try4
try4 == test


Dynamically expanded example:

    def hello():
        print('hellowording..')
    hello()

End of file.

Out[111]:
True

In [112]:
print expand('replace me with something', 'me', 'something')


replace something with something

In [ ]: