Last active
March 17, 2023 17:38
-
-
Save msullivan/169cd943414aeab0a86b6cad7d010e8a to your computer and use it in GitHub Desktop.
Library for dynamically building properly indented output using f strings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import textwrap | |
from typing import Any | |
# Escape delimeters for maintaining a nesting structure in strings | |
# that the user produces. Obviously, as with all schemes for in-band | |
# signalling, all hell can break loose if the signals appear in the | |
# input data unescaped. | |
# | |
# Our signal sequences contain a null byte and both kinds of quote | |
# character, so you should be fine as long as any untrusted data | |
# either: | |
# * Has no null bytes | |
# * Has at least one kind of quote character escaped in it somehow | |
LEFT_ESCAPE = "\0'\"<<{<[<[{{<!!!" | |
RIGHT_ESCAPE = "\0'\"!!!>}}]>]>}>>" | |
ESCAPE_LEN = len(LEFT_ESCAPE) | |
assert len(RIGHT_ESCAPE) == ESCAPE_LEN | |
LINE_BLANK = LEFT_ESCAPE[:-1] + "||||||" + RIGHT_ESCAPE[1:] | |
def escape(s: str) -> str: | |
return LEFT_ESCAPE + s.strip('\n') + RIGHT_ESCAPE | |
Rep = list[str | list[Any]] | |
def parse(s: str, start: int) -> tuple[Rep, int]: | |
frags = [] | |
while start < len(s): | |
nleft = s.find(LEFT_ESCAPE, start) | |
nright = s.find(RIGHT_ESCAPE, start) | |
if nleft == nright == -1: | |
frags.append(s[start:]) | |
start = len(s) | |
elif nleft != -1 and nleft < nright: | |
if nleft > start: | |
frags.append(s[start:nleft]) | |
subfrag, start = parse(s, nleft + ESCAPE_LEN) | |
# If it is the special magic line blanking fragment, | |
# delete up through the last newline. Otherwise collect it. | |
if subfrag == [LINE_BLANK] and frags: | |
frags[-1] = frags[-1].rsplit('\n', 1)[0] | |
else: | |
frags.append(subfrag) | |
else: | |
assert nright >= 0 | |
frags.append(s[start:nright]) | |
start = nright + ESCAPE_LEN | |
break | |
return frags, start | |
def format_rep(rep: Rep) -> str: | |
# cpython does some really dubious things to make appending in place | |
# to a string efficient, and we depend on them here | |
out_str = "" | |
# TODO: I think there ought to be a more complicated algorithm | |
# that builds a list of lines + indentation metadata and then | |
# fixes it all up in one go? | |
for frag in rep: | |
if isinstance(frag, str): | |
out_str += frag | |
else: | |
fixed_frag = format_rep(frag) | |
# If there is a newline in the final result, we need to indent | |
# it to our current position on the current line. | |
if '\n' in fixed_frag: | |
last_nl = out_str.rfind('\n') | |
indent = 0 if last_nl < 0 else len(out_str) - last_nl - 1 | |
# Indent all the lines but the first (since that goes | |
# onto our current line) | |
fixed_frag = textwrap.indent(fixed_frag, ' ' * indent)[indent:] | |
out_str += fixed_frag | |
return textwrap.dedent(out_str).removesuffix('\n') | |
def xdedent(s: str) -> str: | |
# Hate needing the leading slash | |
s = s.removeprefix('\n') | |
parsed, _ = parse(s, 0) | |
return format_rep(parsed) | |
##### | |
X = escape | |
EXPECTED_1 = ''' | |
call_something() | |
foo = 10 | |
while True: | |
if bar: | |
do_something( | |
foo, | |
bar, | |
[1, 2, 3, | |
4, 5, 6], | |
reify( | |
spam | |
), | |
reify( | |
eggs | |
), | |
reify( | |
ham | |
) | |
) | |
another | |
more | |
'''.strip('\n') | |
EXPECTED_2 = ''' | |
call_something() | |
foo = 10 | |
while True: | |
if bar: | |
another | |
more | |
'''.strip('\n') | |
def test_1(do_something=True): | |
foo = 'foo' | |
bar = 'bar' | |
left = ''' | |
[1, 2, 3, | |
4, 5, 6] | |
''' | |
things = [] | |
for thing in ['spam', 'eggs', 'ham']: | |
things.append(f''' | |
reify( | |
{thing} | |
) | |
''') | |
sep = ",\n" | |
if do_something: | |
orig = f''' | |
do_something( | |
{foo}, | |
{bar}, | |
{X(left)}, | |
{X(sep.join(X(x) for x in things))} | |
) | |
''' | |
else: | |
orig = LINE_BLANK | |
return xdedent(f''' | |
call_something() | |
{X(foo)} = 10 | |
while True: | |
if {bar}: | |
{X(orig)} | |
another | |
more | |
''') | |
assert test_1(do_something=True) == EXPECTED_1 | |
assert test_1(do_something=False) == EXPECTED_2 | |
def test_2(): | |
import itertools | |
dels = { | |
'a': '(DELETE DeleteTest)', | |
'b': '(DELETE LinkingType)', | |
} | |
for binds, uses in itertools.product( | |
list(itertools.permutations(dels.keys())), | |
list(itertools.permutations(dels.keys())), | |
): | |
nl = '\n' | |
q = xdedent(f''' | |
with | |
{X(nl.join(X(f'{k} := {dels[k]},') for k in binds))} | |
select {{{', '.join(uses)}}}; | |
''') | |
print(q) | |
test_2() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment