From a73aba3e35e1e545668166b1cdecf6f3b85eb6cd Mon Sep 17 00:00:00 2001 From: Mohammed Anas Nathani Date: Thu, 23 Jul 2026 03:59:27 +0530 Subject: [PATCH] refactor: extract DSL escaping helpers into escaping.py Move DistillError, _grep_flags_and_pattern, _VHS_REGEX_METAS, vhs_wait_pattern, and vhs_quote into a new module and re-export them from distill so existing imports keep working. Zero behavior change; grounding code stays in distill.py. Closes #195 --- src/readme2demo/distill.py | 68 +++++-------------------------------- src/readme2demo/escaping.py | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 60 deletions(-) create mode 100644 src/readme2demo/escaping.py diff --git a/src/readme2demo/distill.py b/src/readme2demo/distill.py index a84beca..8189082 100644 --- a/src/readme2demo/distill.py +++ b/src/readme2demo/distill.py @@ -31,6 +31,14 @@ from readme2demo import llm from readme2demo.types import CommandLog, DistillOutput, Plan, TapeCommand +from readme2demo.escaping import ( # noqa: F401 — re-export, callers import from distill + DistillError, + _VHS_REGEX_METAS, + _grep_flags_and_pattern, + vhs_quote, + vhs_wait_pattern, +) + _PROMPTS_DIR = Path(__file__).resolve().parent / "prompts" _TEMPLATES_DIR = Path(__file__).resolve().parent / "templates" @@ -65,19 +73,6 @@ def heredoc_prefix(cmd: str) -> Optional[str]: return " ".join(cmd[: m.start()].split()).strip() -class DistillError(RuntimeError): - """Raised when the distiller cannot produce a fully grounded output. - - Carries ``cost_usd`` so spend already incurred before the failure is not - lost: the grounding retry means this error can arrive *after* two paid - LLM calls, and the orchestrator records it against the failed stage. - """ - - def __init__(self, *args, cost_usd: float = 0.0) -> None: - super().__init__(*args) - self.cost_usd = cost_usd - - # -- grounding ---------------------------------------------------------------- @@ -350,18 +345,6 @@ def run_distiller( # -- artifact writing --------------------------------------------------------- -def _grep_flags_and_pattern(pattern: str) -> tuple[str, str]: - """Translate a Python-style regex to grep -E usage. - - GNU grep -E does not understand inline flags like ``(?i)``; the planner - (an LLM) writes Python-style patterns. Handle the common case by - stripping a leading ``(?i)`` and adding grep's ``-i`` flag. - """ - if pattern.startswith("(?i)"): - return "-qiE", pattern[4:] - return "-qE", pattern - - def _tolerate_findings_steps(commands: list[str], log: CommandLog | None) -> list[str]: """Append ``|| true`` to step commands that legitimately exit nonzero. @@ -455,41 +438,6 @@ def _render_commands_sh(out: DistillOutput, plan: Plan, repo_url: str, log: "Com return "\n".join(lines) + "\n" -# Go-regexp metacharacters (VHS Wait+Screen patterns), plus the / delimiter. -_VHS_REGEX_METAS = set("\\.+*?()|[]{}^$/") - - -def vhs_wait_pattern(s: str, max_len: int = 40) -> str: - """Make a Wait+Screen pattern safe: treat it as a LITERAL substring. - - The distiller is told to use plain substrings, but an LLM instruction is - not enforcement — e.g. "ToolHive (thv) is a lightweight" silently becomes - a regex with a capture group that never matches the on-screen parens and - times the render out. Escape every metacharacter, and truncate so the - pattern can't span a wrapped terminal line. - """ - s = s[:max_len] - return "".join("\\" + ch if ch in _VHS_REGEX_METAS else ch for ch in s) - - -def vhs_quote(s: str) -> str: - """Quote a string for a VHS ``Type`` argument. - - VHS string literals do not support backslash escapes; instead VHS accepts - three delimiters. Pick one the string doesn't contain: double quotes, - then backticks, then single quotes. - """ - if '"' not in s: - return f'"{s}"' - if "`" not in s: - return f"`{s}`" - if "'" not in s: - return f"'{s}'" - raise DistillError( - f"Command cannot be quoted for VHS (contains \", ` and '): {s!r}" - ) - - def write_commands_sh(out: DistillOutput, run_dir: Path, plan: Plan, repo_url: str, log: "CommandLog | None" = None) -> Path: """Write the executable commands.sh (header + clone preamble + assertion).""" run_dir.mkdir(parents=True, exist_ok=True) diff --git a/src/readme2demo/escaping.py b/src/readme2demo/escaping.py new file mode 100644 index 0000000..4384437 --- /dev/null +++ b/src/readme2demo/escaping.py @@ -0,0 +1,67 @@ +"""DSL escaping helpers for VHS tape syntax and grep -E patterns. + +Extracted from distill.py so the pure string-escaping surface can evolve +without touching grounding logic. +""" + +from __future__ import annotations + + +class DistillError(RuntimeError): + """Raised when the distiller cannot produce a fully grounded output. + + Carries ``cost_usd`` so spend already incurred before the failure is not + lost: the grounding retry means this error can arrive *after* two paid + LLM calls, and the orchestrator records it against the failed stage. + """ + + def __init__(self, *args, cost_usd: float = 0.0) -> None: + super().__init__(*args) + self.cost_usd = cost_usd + + +def _grep_flags_and_pattern(pattern: str) -> tuple[str, str]: + """Translate a Python-style regex to grep -E usage. + + GNU grep -E does not understand inline flags like ``(?i)``; the planner + (an LLM) writes Python-style patterns. Handle the common case by + stripping a leading ``(?i)`` and adding grep's ``-i`` flag. + """ + if pattern.startswith("(?i)"): + return "-qiE", pattern[4:] + return "-qE", pattern + + +# Go-regexp metacharacters (VHS Wait+Screen patterns), plus the / delimiter. +_VHS_REGEX_METAS = set("\\.+*?()|[]{}^$/") + + +def vhs_wait_pattern(s: str, max_len: int = 40) -> str: + """Make a Wait+Screen pattern safe: treat it as a LITERAL substring. + + The distiller is told to use plain substrings, but an LLM instruction is + not enforcement — e.g. "ToolHive (thv) is a lightweight" silently becomes + a regex with a capture group that never matches the on-screen parens and + times the render out. Escape every metacharacter, and truncate so the + pattern can't span a wrapped terminal line. + """ + s = s[:max_len] + return "".join("\\" + ch if ch in _VHS_REGEX_METAS else ch for ch in s) + + +def vhs_quote(s: str) -> str: + """Quote a string for a VHS ``Type`` argument. + + VHS string literals do not support backslash escapes; instead VHS accepts + three delimiters. Pick one the string doesn't contain: double quotes, + then backticks, then single quotes. + """ + if '"' not in s: + return f'"{s}"' + if "`" not in s: + return f"`{s}`" + if "'" not in s: + return f"'{s}'" + raise DistillError( + f"Command cannot be quoted for VHS (contains \", ` and '): {s!r}" + )