CurioDidact
CurioDidact

Reputation: 71

Preserving indentation in a triple-quoted fstring

I am trying to use a triple-quoted strings in Python3(.7) to build some formated strings.

I have a list of inner strings, which all need to be tabbed in:

    This is some text
    across multiple
    lines.

And a string which should contain the inner string

data{
    // string goes here
}

I cannot tab the inner string when I create it. So, my thought was to use dedent with Python3 triple-quoted fstrings:

import textwrap

inner_str = textwrap.dedent(
    '''\
    This is some text
    across multiple
    lines.'''
)

full_str = textwrap.dedent(
    f'''\
    data{{
        // This should all be tabbed
        {inner_str}
    }}'''
)

print(full_str)

However, the indentation is not maintained:

    data{
        // This should all be tabbed
        This is some text
across multiple
lines.
    }

The desired result:

data{
    // This should all be tabbed
    This is some text
    across multiple
    lines.
}

How can I preserve the indentation of the fstring without pre-tabbing the inner string?

Upvotes: 7

Views: 3168

Answers (3)

DaveStSomeWhere
DaveStSomeWhere

Reputation: 2540

Edited to avoid tabbing inner_str.

import textwrap

line_tab = '\n\t'

inner_str = f'''\
This is some text
across multiple
lines.
'''

full_str = textwrap.dedent(f'''\
data{{
    // This should all be tabbed 
    {line_tab.join(inner_str.splitlines())}
}}''')
                           )

print(full_str)

Output:

data{
    // This should all be tabbed 
    This is some text
    across multiple
    lines.
}

Upvotes: 0

GZ0
GZ0

Reputation: 4268

This seems to provide what you want.

import textwrap

inner_str = textwrap.dedent(
    '''\
    This is some text
    across multiple
    lines.'''
)

full_str = textwrap.dedent(
    f'''
    data{{
{textwrap.indent(inner_str, "        ")}
    }}'''
)

A better solution:

idt = str.maketrans({'\n': "\n        "})
print(textwrap.dedent(
    f'''
    data{{
        {inner_str.translate(idt)}
    }}'''
))

Another solution with customized tab width:

def indent_inner(inner_str, indent):
    return inner_str.replace('\n', '\n' + indent)   # os.linesep could be used if the function is needed across different OSs

print(textwrap.dedent(
    f'''
    data{{
        {indent_inner(inner_str, "        ")}
    }}'''
))

Upvotes: 4

CurioDidact
CurioDidact

Reputation: 71

None of the answers here seemed to do what I want, so I'm answering my own question with a solution which gets as close to what I was looking for as I can make. It is not required to pre-tab the data to define its indentation level, nor is it required to not indent the line (which breaks readability). Instead, the indentation level of the current line in the fstring is passed at usage time.

Not perfect but it works.

import textwrap


def code_indent(text, tab_sz, tab_chr=' '):
    def indented_lines():
        for i, line in enumerate(text.splitlines(True)):
            yield (
                tab_chr * tab_sz + line if line.strip() else line
            ) if i else line
    return ''.join(indented_lines())


inner_str = textwrap.dedent(
    '''\
    This is some text
    across multiple
    lines.'''
)


full_str = textwrap.dedent(
    f'''
    data{{
        {code_indent(inner_str, 8)}
    }}'''
)

print(full_str)

Upvotes: 0

Related Questions