53 lines
1.6 KiB
Python
53 lines
1.6 KiB
Python
|
import bisect
|
||
|
import re
|
||
|
from collections import defaultdict
|
||
|
from typing import Optional, Tuple
|
||
|
|
||
|
IDENTIFIER_RE = re.compile(r"(?<!%)(?:%(\w+)\b|%{(\w+)})")
|
||
|
|
||
|
|
||
|
def fill_template(template_str: str) -> Tuple[Optional[str], str]:
|
||
|
lines = template_str.splitlines()
|
||
|
|
||
|
identifier_contexts = defaultdict(list)
|
||
|
|
||
|
for i, line in enumerate(lines):
|
||
|
for match in re.finditer(IDENTIFIER_RE, line):
|
||
|
name = match[1] or match[2]
|
||
|
for j in range(max(0, i - 2), min(len(lines) - 1, i + 2)):
|
||
|
ctx = identifier_contexts[name]
|
||
|
idx = bisect.bisect_left(ctx, j)
|
||
|
if idx >= len(ctx) or ctx[idx] != j:
|
||
|
ctx.insert(idx, j)
|
||
|
|
||
|
replacements = {}
|
||
|
for identifier, ctx in identifier_contexts.items():
|
||
|
|
||
|
def colorize(match):
|
||
|
name = match[1] or match[2]
|
||
|
if name == identifier:
|
||
|
return f"\x1b[31m%{match[1] or match[2]}\x1b[0m"
|
||
|
else:
|
||
|
return match[0]
|
||
|
|
||
|
prev = 0
|
||
|
printed = 0
|
||
|
|
||
|
for line_idx in ctx:
|
||
|
print(re.sub(IDENTIFIER_RE, colorize, lines[line_idx]))
|
||
|
printed += 1
|
||
|
if prev and line_idx - prev > 1:
|
||
|
print("\x1b[1m---\x1b[0m")
|
||
|
printed += 1
|
||
|
prev = line_idx
|
||
|
|
||
|
replacements[identifier] = input("\x1b[1m => " + identifier + ":\x1b[0m ")
|
||
|
printed += 1
|
||
|
|
||
|
print(f"\r\x1b[{printed}F\x1b[0J", end="")
|
||
|
|
||
|
def replace(match):
|
||
|
return replacements[match[1] or match[2]]
|
||
|
|
||
|
return replacements.get("name"), re.sub(IDENTIFIER_RE, replace, template_str).replace("%%","%")
|