What follows the first line of a multiline string is part of the string, and not treated as indentation by the parser. You may freely write:
def main():
"""foo
bar
foo2"""
pass
and it will do the right thing.
On the other hand, that's not readable, and Python knows it. So if a docstring contains whitespace in it's second line, that amount of whitespace is stripped off when you use help() to view the docstring. Thus, help(main) and the below help(main2) produce the same help info.
So if I get it correctly, you take whatever the user inputs, indent it properly and add it to the rest of your program (and then run that whole program).
So after you put the user input into your program, you could run a regex, that basically takes that forced indentation back. Something like: Within three quotes, replace all "new line markers" followed by four spaces (or a tab) with only a "new line marker".
From what I see, a better answer here might be inspect.cleandoc, which does much of what textwrap.dedent does but also fixes the problems that textwrap.dedent has with the leading line.
I wanted to preserve exactly what is between the triple-quote lines, removing common leading indent only. I found that texwrap.dedent and inspect.cleandoc didn't do it quite right, so I wrote this one. It uses os.path.commonprefix.
import re
from os.path import commonprefix
def ql(s, eol=True):
lines = s.splitlines()
l0 = None
if lines:
l0 = lines.pop(0) or None
common = commonprefix(lines)
indent = re.match(r'\s*', common)[0]
n = len(indent)
lines2 = [l[n:] for l in lines]
if not eol and lines2 and not lines2[-1]:
lines2.pop()
if l0 is not None:
lines2.insert(0, l0)
s2 = "\n".join(lines2)
return s2
This can quote any string with any indent. I wanted it to include the trailing newline by default, but with an option to remove it so that it can quote any string neatly.
I had a similar issue: I wanted my triple quoted string to be indented, but I didn't want the string to have all those spaces at the beginning of each line. I used re to correct my issue:
print(re.sub('\n *','\n', f"""Content-Type: multipart/mixed; boundary="===============9004758485092194316=="
` MIME-Version: 1.0
Subject: Get the reader's attention here!
To: recipient@email.com
--===============9004758485092194316==
Content-Type: text/html; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Very important message goes here - you can even use <b>HTML</b>.
--===============9004758485092194316==--
"""))
Above, I was able to keep my code indented, but the string was left trimmed essentially. All spaces at the beginning of each line were deleted. This was important since any spaces in front of the SMTP or MIME specific lines would break the email message.
The tradeoff I made was that I left the Content-Type on the first line because the regex I was using didn't remove the initial \n (which broke email). If it bothered me enough, I guess I could have added an lstrip like this:
This does the trick, if I understand the question correctly. lstrip() removes leading whitespace, so it will remove
tabs as well as spaces.
from os import linesep
def dedent(message):
return linesep.join(line.lstrip() for line in message.splitlines())
Example:
name='host'
config_file='/Users/nmellor/code/cold_fusion/end-to-end/config/stage.toml'
message = f"""Missing env var or configuration entry for 'host'.
Please add '{name}' entry to file
{config_file}
or export environment variable 'mqtt_{name}' before
running the program.
"""
>>> print(message)
Missing env var or configuration entry for 'host'.
Please add 'host' entry to
'/Users/nmellor/code/cold_fusion/end-to-end/config/stage.toml'
or export environment variable 'mqtt_host' before
running the program.
>>> print(dedent(message))
Missing env var or configuration entry for 'host'.
Please add 'host' entry to file
'/Users/nmellor/code/cold_fusion/end-to-end/config/stage.toml'
or export environment variable 'mqtt_host' before
running the program.
The above solution will remove ALL indentation. If you want to remove indentation that is common to the whole multiline string, use textwrap.dedent(). But take care that the first and last lines in the multi-line string are also indented otherwise .dedent() will do nothing.