Welcome, guest | Sign In | My Account | Store | Cart

An extension of Python's 'Hello {fieldname}!'.format(fieldname='world') functionality for multi-line strings. When {fieldname} is indented, all the lines in the inserted fieldvalue are indented to the same amount.

Python, 73 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import string
import sys

class IndentFormatter(string.Formatter):
    def parse(self,format_string):
        parsed = string.Formatter.parse(self,format_string)
        for (literal_text, field_name, format_spec, conversion) in parsed:
            last_newline = literal_text.rfind('\n')
            indentation = literal_text[last_newline+1:]
            if indentation.isspace():
                format_spec = '|{0}|{1}'.format(len(indentation),format_spec)
            yield literal_text, field_name, format_spec, conversion
    def format_field(self,value,format_spec):
        if format_spec.startswith('|'):
            nspaces,_,old_format_spec = format_spec[1:].partition('|')
            return trim(string.Formatter.format_field(self,value,old_format_spec),int(nspaces))
        else:
            return string.Formatter.format_field(self,value,format_spec)
        
# from PEP 257, extended with addindent
def trim(docstring,addindent=0):
    if not docstring:
        return ''
    # Convert tabs to spaces (following the normal Python rules)
    # and split into a list of lines:
    lines = docstring.expandtabs().splitlines()
    # Determine minimum indentation (first line doesn't count):
    indent = sys.maxint
    for line in lines[1:]:
        stripped = line.lstrip()
        if stripped:
            indent = min(indent, len(line) - len(stripped))
    # Remove indentation (first line is special):
    trimmed = [lines[0].strip()]
    if indent < sys.maxint:
        for line in lines[1:]:
            trimmed.append(line[indent:].rstrip())
    # Strip off trailing and leading blank lines:
    while trimmed and not trimmed[-1]:
        trimmed.pop()
    while trimmed and not trimmed[0]:
        trimmed.pop(0)
    # Return a single string:
    return ('\n'+' '*addindent).join(trimmed)        

class C(str):
    def format(self,*args,**kwargs):
        return C(IndentFormatter().format(self,*args,**kwargs))

def test():
    arrayvars = ['arr{n}'.format(n=n) for n in range(3)]
    array_length = 1000;
    array_args = ', '.join('int* {var}'.format(var=var) for var in arrayvars)
    array_product = ' * '.join('{var}[i]'.format(var=var) for var in arrayvars)
    freearrays = '\n'.join('free({var});'.format(var=var) for var in arrayvars)
    body = C('''
             candidate = {array_product};
             if (candidate>max)
                max = candidate;
             ''').format(**locals())
    fun = C('''
            int max_product({array_args}) {{
                int max = 0;
                int candidate;
                int i;
                for (i=0; i<{array_length}; i++) {{
                    {body}
                }}
                {freearrays}
                return max;
            }}
            ''').format(**locals())
    print(fun)

As you see, I use this as a simple solution for C code generation from within Python. The example in test() will print

>>> test()
            int max_product(int* arr0, int* arr1, int* arr2) {
                int max = 0;
                int candidate;
                int i;
                for (i=0; i<1000; i++) {
                    candidate = arr0[i] * arr1[i] * arr2[i];
                    if (candidate>max)
                        max = candidate;
                }
                free(arr0);
                free(arr1);
                free(arr2);
                return max;
            }

The code to be inserted is first aligned using the docstring convention. Then, each line starting from the second is prefixed with the same amount of spaces that are found between the insertion point and the last preceding newline. (Tabs won't work, and should be banned from Python code anyway.)

I use a convenience class C here to reduce keystrokes and screen clutter. Perhaps it could be extended so that operations like + or join on a C instance return other C instances, but I haven't really seen the use for this yet. Likewise, I am not sure if the format method should return a C; it just feels right somehow.

Discussion welcome! Am I reinventing the wheel here? (I know about templating engines like Cheetah but they are a little too heavy for my simple needs.)

1 comment

Maurice van der Pot 8 years, 9 months ago  # | flag

Thanks for this piece of code! It was exactly what we were looking for.

We did have to make a minor fix though. The trim function in the original version of the code strips trailing spaces off of the text to be inserted into a field. This is fine for docstrings, but not for this code if the field is followed by more text on the same line. So for instance inserting 'return ' into

'''    {optionalreturn}function();'''

would give

'''    returnfunction();'''

We fixed it by replacing strip with lstrip on line 34 and removing rstrip from line 37:

# Remove indentation (first line is special):
trimmed = [lines[0].lstrip()]
if indent < sys.maxint:
    for line in lines[1:]:
        trimmed.append(line[indent:])
Created by Sander Evers on Wed, 19 Feb 2014 (MIT)
Python recipes (4591)
Sander Evers's recipes (6)

Required Modules

  • (none specified)

Other Information and Tasks