akim
akim

Reputation: 8759

Indentation preserving formatting of multiline strings

I generate bits of (C++) code using format strings such as

memfn_declaration = '''\
  {doc}
  {static}auto {fun}({formals}){const}
    -> {result};
'''

Format strings are nice for this, but is there a way to make them keep the indentation level for {doc} here? It's typically multiline.

I know I can just indent the string corresponding to doc by two spaces, I know there are plenty of functions to this end, but that's not what I'm asking: I'm looking for something that would work without tweaking the strings I pass.

Upvotes: 1

Views: 618

Answers (3)

martineau
martineau

Reputation: 123453

Now that you've posted your own answer and clarified at little more about what you want. I think it would be slightly better to implement it by defining your own str subclass that extends the way strings can be formatted by supporting a new conversion type, 'i', which must be followed by a decimal number representing the level of indentation desired.

Here's an implementation that works in both Python 2 & 3:

import re
from textwrap import dedent
try:
    from textwrap import indent
except ImportError:
    def indent(s, prefix):
        return prefix + prefix.join(s.splitlines(True))

class Indentable(str):
    indent_format_spec = re.compile(r'''i([0-9]+)''')

    def __format__(self, format_spec):
        matches = self.indent_format_spec.search(format_spec)
        if matches:
            level = int(matches.group(1))
            first, sep, text = dedent(self).strip().partition('\n')
            return first + sep + indent(text, ' ' * level)

        return super(Indentable, self).__format__(format_spec)

sample_format_string = '''\
  {doc:i2}
  {static}auto {fun}({formals}){const}
    -> {result};
'''

specs = {
    'doc': Indentable('''
        // Convert a string to a float.
        // Quite obsolete.
        // Use something better instead.
    '''),
    'static': '',
    'fun': 'atof',
    'formals': 'const char*',
    'const': '',
    'result': 'float',
}

print(sample_format_string.format(**specs))

Output:

  // Convert a string to a float.
  // Quite obsolete.
  // Use something better instead.
  auto atof(const char*)
    -> float;

Upvotes: 1

akim
akim

Reputation: 8759

I'm looking for something which is light-weight on the caller site. I can live with the following compromise: I use the fact that format strings are allowed to query for attributes (0.foo) or items (0['foo']) to pass the indentation level in the format string.

import textwrap

class Indent(str):
    def __new__(cls, *args, **kwargs):
        return super(Indent, cls).__new__(cls, *args, **kwargs)

    def __getitem__(self, level):
        first, _, text = textwrap.dedent(self).strip().partition('\n')
        text = textwrap.indent(text, ' ' * level)
        return first + '\n' + text

def indent_doc(d):
    res = dict(d)
    res['doc'] = Indent(d['doc'])
    return res

format = '''
  {doc[2]}
  {static}auto {fun}({formals}){const}
    -> {result};
'''

specs = {
    'doc': '''
    // Convert a string to a float.
    // Quite obsolete.
    // Use something better instead.
    ''',
    'static': '',
    'fun': 'atof',
    'formals': 'const char*',
    'const': '',
    'result': 'float',
}
print(format.format_map(indent_doc(specs)))

that gives:

$ python /tmp/foo.py

  // Convert a string to a float.
  // Quite obsolete.
  // Use something better instead.
  auto atof(const char*)
    -> float;

I'd be happy to read opinions about that.

Upvotes: 0

kevr
kevr

Reputation: 455

The issue is this:

'''\

The backslash will ignore all whitespaces up to the next character. Remove the backslash.

Upvotes: 0

Related Questions