shopstack / tools /fix_primitives_fstrings.py
pranaysuyash's picture
Sync ShopStack 2026-06-15 round 2: primary-nav More, undo bar, freshness stamps, safe_render_html, home-flow TabContext
af69759 verified
Raw
History Blame Contribute Delete
3.78 kB
"""Aggressively fix all broken f-strings in primitives.py."""
import ast
import sys
import re
path = "shopstack/ui/components/primitives.py"
with open(path) as f:
content = f.read()
# Strategy: replace ALL f'...' with f"..." (and handle the closing quote properly)
# Then handle the remaining issues
lines = content.split("\n")
fixed_lines = []
for line in lines:
original = line
# Fix 1: f"...content with " inside..." β†’ f'...content with " inside...'
# We need to be careful: the f-string might span multiple lines
# For now, handle single-line cases
# Fix 2: f'...content...' where content has ' β†’ change to f"..."
if "f'" in line:
# Check if this is a broken f-string
# Simple heuristic: count single quotes in the f-string portion
# If > 2, it's broken
# Find the f' and the next non-escaped '
stripped = line.lstrip()
if stripped.startswith("f'"):
# Find the matching closing '
# The f-string ends with ' (possibly followed by > or })
# But if there are nested ', the first ' closes the f-string
# We need to find the LAST ' before end of expression
# Simple approach: find the last ' in the line
rest = stripped[2:]
# The f-string should end with ' (possibly followed by > or })
# But if there are more than 2 ', we need to fix
quote_count = rest.count("'")
if quote_count > 1:
# Find the last '
last_quote = rest.rfind("'")
inner = rest[:last_quote]
after = rest[last_quote + 1:]
indent = line[:len(line) - len(stripped)]
line = indent + 'f"' + inner + '"' + after
# Fix 3: f"...content with " inside..." β†’ f'...content with " inside...'
if 'f"' in line:
stripped = line.lstrip()
if stripped.startswith('f"'):
rest = stripped[2:]
# Check if the f-string has nested "
# Simple heuristic: if the f-string is on a single line and has > 2 "
if not stripped.startswith('f"""'):
quote_count = rest.count('"')
if quote_count > 1:
last_quote = rest.rfind('"')
inner = rest[:last_quote]
after = rest[last_quote + 1:]
# Only fix if the inner has " (nested quotes)
if '"' in inner:
indent = line[:len(line) - len(stripped)]
line = indent + "f'" + inner + "'" + after
# Fix 4: f" that ends with ' (mismatched quotes)
# Pattern: f"...content...' (should be f"...content...")
if 'f"' in line:
# Check if the line ends with ' (single quote) but starts with f"
stripped = line.rstrip()
if stripped.startswith('f"') and stripped.endswith("'"):
# Change the ending ' to "
line = line.rstrip() + '"'
# Fix 5: </span>' β†’ </span>" (missing closing quote on f-string)
line = line.replace("</span>' +", '</span>" +')
line = line.replace("</span>'\n", '</span>"\n')
line = line.replace("</div>'\n", '</div>"\n')
line = line.replace("</div>' +", '</div>" +')
fixed_lines.append(line)
fixed = "\n".join(fixed_lines)
with open(path, "w") as f:
f.write(fixed)
# Check syntax
try:
ast.parse(fixed)
print("OK - file parses")
sys.exit(0)
except SyntaxError as e:
print(f"Still broken at line {e.lineno}: {e.msg}")
lines = fixed.split("\n")
for i in range(max(0, e.lineno - 3), min(len(lines), e.lineno + 3)):
marker = ">>>" if i + 1 == e.lineno else " "
print(f"{marker} {i + 1}: {lines[i]}")
sys.exit(1)