Skip to content

Instantly share code, notes, and snippets.

@jasperf
Created March 14, 2026 05:36
Show Gist options
  • Select an option

  • Save jasperf/45e14a3388eea86c01aad01558379da4 to your computer and use it in GitHub Desktop.

Select an option

Save jasperf/45e14a3388eea86c01aad01558379da4 to your computer and use it in GitHub Desktop.
uzzy-match and replace a WordPress block comment in a pattern file.
#!/usr/bin/env python3
"""
patch-block.py — Fuzzy-match and replace a WordPress block comment in a pattern file.
Solves the problem where AI tools generate search text with minor structural
differences (e.g., wrong brace count) causing exact search/replace to fail
despite 99%+ similarity. Uses the same fuzzy matching that already correctly
identifies the right block, and actually applies the replacement.
How it works:
1. Finds all <!-- wp:blockname {...} --> comments in the file
2. Scores each against the --search text using string similarity
3. If best match >= threshold (default 95%), replaces it with --replace text
4. Validates replacement JSON before writing
Usage:
# Dry-run to preview
python3 scripts/elayne/patch-block.py FILE --search 'OLD_BLOCK' --replace 'NEW_BLOCK' --dry-run
# Apply change
python3 scripts/elayne/patch-block.py FILE --search 'OLD_BLOCK' --replace 'NEW_BLOCK'
# Lower threshold if needed (not recommended below 0.90)
python3 scripts/elayne/patch-block.py FILE --search 'OLD_BLOCK' --replace 'NEW_BLOCK' --threshold 0.90
Notes:
- --search can have wrong brace counts / minor JSON differences; fuzzy matching handles it
- --replace must contain valid JSON (script validates before writing)
- Always run --dry-run first to confirm the right block is matched
"""
import re
import json
import sys
import difflib
import argparse
from pathlib import Path
def find_json_end(text: str, start: int) -> int:
"""Find the closing brace index for a JSON object starting at `start`."""
depth = 0
in_string = False
escape_next = False
for i in range(start, len(text)):
ch = text[i]
if escape_next:
escape_next = False
continue
if ch == '\\' and in_string:
escape_next = True
continue
if ch == '"':
in_string = not in_string
continue
if in_string:
continue
if ch == '{':
depth += 1
elif ch == '}':
depth -= 1
if depth == 0:
return i
return -1
def find_block_comments(content: str) -> list:
"""
Find all WordPress block opening comments with JSON attributes.
Returns list of (start, end, raw_string) tuples (end is exclusive).
"""
pattern = re.compile(r'<!-- wp:[\w/:-]+\s+\{')
results = []
for match in pattern.finditer(content):
json_start = match.end() - 1 # position of opening '{'
json_end = find_json_end(content, json_start)
if json_end == -1:
continue
after = content[json_end + 1:]
close = re.match(r'\s*/?-->', after)
if not close:
continue
end_pos = json_end + 1 + close.end()
raw = content[match.start():end_pos]
results.append((match.start(), end_pos, raw))
return results
def extract_block_json(block_comment: str):
"""
Parse the JSON from a block comment string.
Returns parsed dict or None if JSON is invalid.
"""
json_match = re.search(r'\{', block_comment)
if not json_match:
return None
json_start = json_match.start()
json_end = find_json_end(block_comment, json_start)
if json_end == -1:
return None
try:
return json.loads(block_comment[json_start:json_end + 1])
except json.JSONDecodeError:
return None
def validate_replacement(replace_text: str) -> tuple:
"""
Validate that the replacement is a well-formed block comment with valid JSON.
Returns (ok: bool, error_message: str).
"""
if not re.match(r'<!-- wp:[\w/:-]+', replace_text):
return False, "Does not start with <!-- wp:blockname"
if not replace_text.rstrip().endswith('-->'):
return False, "Does not end with -->"
parsed = extract_block_json(replace_text)
if parsed is None:
return False, "JSON inside block comment is invalid"
return True, ""
def main():
parser = argparse.ArgumentParser(
description='Fuzzy-match and replace a WordPress block comment.',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument('file', help='PHP pattern file to patch')
parser.add_argument('--search', required=True,
help='Block comment to find (fuzzy — brace count errors OK)')
parser.add_argument('--replace', required=True,
help='Block comment to replace with (must have valid JSON)')
parser.add_argument('--dry-run', action='store_true',
help='Show diff without writing')
parser.add_argument('--threshold', type=float, default=0.95,
help='Minimum similarity to accept match (default: 0.95)')
args = parser.parse_args()
path = Path(args.file)
if not path.exists():
print(f'ERROR: {args.file} not found', file=sys.stderr)
sys.exit(1)
# Validate replacement before touching the file
ok, err = validate_replacement(args.replace)
if not ok:
print(f'ERROR: Invalid --replace text: {err}', file=sys.stderr)
sys.exit(1)
content = path.read_text(encoding='utf-8')
blocks = find_block_comments(content)
if not blocks:
print('ERROR: No block comments with JSON found in file', file=sys.stderr)
sys.exit(1)
# Score each block comment against the search text
scored = [
(difflib.SequenceMatcher(None, args.search, raw).ratio(), start, end, raw)
for start, end, raw in blocks
]
scored.sort(reverse=True)
best_score, best_start, best_end, best_raw = scored[0]
print(f'Best match: {best_score:.1%} similarity')
print(f' {best_raw[:100]}{"..." if len(best_raw) > 100 else ""}')
if best_score < args.threshold:
print(f'\nERROR: Best match {best_score:.1%} is below threshold {args.threshold:.0%}')
print('Try lowering --threshold or check that --search targets the right block.')
sys.exit(1)
if best_raw == args.replace:
print('No change — match is identical to replacement.')
sys.exit(0)
new_content = content[:best_start] + args.replace + content[best_end:]
if args.dry_run:
diff = difflib.unified_diff(
content.splitlines(keepends=True),
new_content.splitlines(keepends=True),
fromfile=f'{args.file} (original)',
tofile=f'{args.file} (patched)',
n=3,
)
print()
print(''.join(list(diff)[:80]))
print('(dry-run — no file written)')
else:
path.write_text(new_content, encoding='utf-8')
print(f'\nPatched: {args.file} (replaced at {best_score:.1%} similarity)')
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment