Restore best-effort Markdown shell-fence formatting (#796)

This commit is contained in:
Glenn Jocher
2026-07-06 19:51:32 +02:00
committed by GitHub
parent 2a67b1bd81
commit 2845ec9c5a
3 changed files with 98 additions and 42 deletions
+1 -1
View File
@@ -28,4 +28,4 @@
# ├── test_summarize_pr.py
# └── ...
__version__ = "0.2.22"
__version__ = "0.2.23"
+70 -18
View File
@@ -11,10 +11,16 @@ from pathlib import Path
def extract_code_blocks(markdown_content):
"""Extract Python code blocks from Markdown content using regex pattern matching."""
"""Extracts Python and Bash code blocks from Markdown content using regex pattern matching."""
# Python code blocks
py_pattern = r"^( *)```(?:python|py|\{[ ]*\.py[ ]*\.annotate[ ]*\})\n(.*?)\n\1```"
py_code_blocks = re.compile(py_pattern, re.DOTALL | re.MULTILINE).findall(markdown_content)
return {"python": py_code_blocks}
# Bash code blocks
bash_pattern = r"^( *)```(?:bash|sh|shell)\n(.*?)\n\1```"
bash_code_blocks = re.compile(bash_pattern, re.DOTALL | re.MULTILINE).findall(markdown_content)
return {"python": py_code_blocks, "bash": bash_code_blocks}
def remove_indentation(code_block, num_spaces):
@@ -90,24 +96,51 @@ def format_code_with_ruff(temp_dir):
print(f"ERROR running Python docstring formatter ❌ {e}")
def format_bash_with_prettier(temp_dir):
"""Formats bash script files in the specified directory using prettier."""
if not next(Path(temp_dir).rglob("*.sh"), None):
return
try:
# Run prettier with explicit config path
result = subprocess.run(
"npx prettier --write --print-width=120 --plugin=$(npm root -g)/prettier-plugin-sh/lib/index.cjs ./**/*.sh",
shell=True, # must use shell=True to expand internal $(cmd)
capture_output=True,
text=True,
)
if result.returncode != 0:
print(f"ERROR running prettier-plugin-sh ❌ {result.stderr}")
else:
print("Completed bash formatting ✅")
except Exception as e:
print(f"ERROR running prettier-plugin-sh ❌ {e}")
def process_markdown_string(
markdown_content: str,
process_python: bool = True,
process_bash: bool = True,
verbose: bool = False,
) -> str:
"""Format Python code blocks inside a Markdown string."""
"""Formats Python/Bash code blocks inside a Markdown string."""
formatted_markdown = markdown_content
with tempfile.TemporaryDirectory() as temp_dir_name:
temp_dir = Path(temp_dir_name)
temp_md = temp_dir / "input.md"
temp_md.write_text(markdown_content, encoding="utf-8")
markdown_snapshot, temp_files = process_markdown_file(temp_md, temp_dir, process_python, verbose)
markdown_snapshot, temp_files = process_markdown_file(temp_md, temp_dir, process_python, process_bash, verbose)
if markdown_snapshot is None or temp_files is None:
return formatted_markdown
if process_python and temp_files:
python_files_exist = process_python and any(code_type == "python" for _, _, _, code_type in temp_files)
bash_files_exist = process_bash and any(code_type == "bash" for _, _, _, code_type in temp_files)
if python_files_exist:
format_code_with_ruff(temp_dir)
if bash_files_exist:
format_bash_with_prettier(temp_dir)
update_markdown_file(temp_md, markdown_snapshot, temp_files)
formatted_markdown = temp_md.read_text(encoding="utf-8")
@@ -115,34 +148,44 @@ def process_markdown_string(
return formatted_markdown
def generate_temp_filename(file_path, index):
def generate_temp_filename(file_path, index, code_type):
"""Creates unique temp filename with full path info for debugging."""
stem = file_path.stem
code_letter = code_type[0] # 'p' for python, 'b' for bash
path_part = str(file_path.parent).replace("/", "_").replace("\\", "_").replace(" ", "-")
hash_val = hashlib.md5(f"{file_path}_{index}".encode()).hexdigest()[:6]
filename = f"{stem}_{path_part}_p{index}_{hash_val}.py"
ext = ".py" if code_type == "python" else ".sh"
filename = f"{stem}_{path_part}_{code_letter}{index}_{hash_val}{ext}"
return re.sub(r"[^\w\-.]", "_", filename)
def process_markdown_file(file_path, temp_dir, process_python=True, verbose=False):
def process_markdown_file(file_path, temp_dir, process_python=True, process_bash=True, verbose=False):
"""Processes a Markdown file, extracting code blocks into temp files and returning the content and file info."""
try:
markdown_content = Path(file_path).read_text(encoding="utf-8")
code_blocks_by_type = extract_code_blocks(markdown_content)
temp_files = []
# Process all code block types based on flags
code_types = []
if process_python:
for i, (num_spaces, code_block) in enumerate(code_blocks_by_type["python"]):
code_types.append(("python", 0))
if process_bash:
code_types.append(("bash", 1000))
for code_type, offset in code_types:
for i, (num_spaces, code_block) in enumerate(code_blocks_by_type[code_type]):
if verbose:
print(f"Extracting python code block {i} from {file_path}")
print(f"Extracting {code_type} code block {i} from {file_path}")
num_spaces = len(num_spaces)
code_without_indentation = remove_indentation(code_block, num_spaces)
temp_file_path = temp_dir / generate_temp_filename(file_path, i)
temp_file_path = temp_dir / generate_temp_filename(file_path, i + offset, code_type)
with open(temp_file_path, "w", encoding="utf-8") as temp_file:
temp_file.write(code_without_indentation)
temp_files.append((num_spaces, code_block, temp_file_path))
temp_files.append((num_spaces, code_block, temp_file_path, code_type))
return markdown_content, temp_files
@@ -153,13 +196,17 @@ def process_markdown_file(file_path, temp_dir, process_python=True, verbose=Fals
def update_markdown_file(file_path, markdown_content, temp_files):
"""Updates a Markdown file with formatted code blocks."""
for num_spaces, original_code_block, temp_file_path in temp_files:
for num_spaces, original_code_block, temp_file_path, code_type in temp_files:
try:
with open(temp_file_path, encoding="utf-8") as temp_file:
formatted_code = temp_file.read().rstrip("\n") # Strip trailing newlines
formatted_code_with_indentation = add_indentation(formatted_code, num_spaces)
for lang in ("python", "py", "{ .py .annotate }"):
# Define the language tags for each code type
lang_tags = {"python": ["python", "py", "{ .py .annotate }"], "bash": ["bash", "sh", "shell"]}
# Replace the code blocks with the formatted version
for lang in lang_tags[code_type]:
markdown_content = markdown_content.replace(
f"{' ' * num_spaces}```{lang}\n{original_code_block}\n{' ' * num_spaces}```",
f"{' ' * num_spaces}```{lang}\n{formatted_code_with_indentation}\n{' ' * num_spaces}```",
@@ -174,8 +221,8 @@ def update_markdown_file(file_path, markdown_content, temp_files):
print(f"Error writing file {file_path}: {e}")
def main(root_dir=Path.cwd(), process_python=True, verbose=False):
"""Process Markdown files, format selected code blocks, and update the original files."""
def main(root_dir=Path.cwd(), process_python=True, process_bash=True, verbose=False):
"""Processes Markdown files, extracts and formats code blocks, and updates the original files."""
root_path = Path(root_dir)
markdown_files = [markdown_file for markdown_file in root_path.rglob("*.md") if not markdown_file.is_symlink()]
temp_dir = Path("temp_code_blocks")
@@ -186,13 +233,18 @@ def main(root_dir=Path.cwd(), process_python=True, verbose=False):
for markdown_file in markdown_files:
if verbose:
print(f"Processing {markdown_file}")
markdown_content, temp_files = process_markdown_file(markdown_file, temp_dir, process_python, verbose)
markdown_content, temp_files = process_markdown_file(
markdown_file, temp_dir, process_python, process_bash, verbose
)
if markdown_content and temp_files:
all_temp_files.append((markdown_file, markdown_content, temp_files))
# Format code blocks based on flags
if process_python:
format_code_with_ruff(temp_dir) # Format Python files
if process_bash:
format_bash_with_prettier(temp_dir) # Format Bash files
# Update Markdown files with formatted code blocks
for markdown_file, markdown_content, temp_files in all_temp_files:
update_markdown_file(markdown_file, markdown_content, temp_files)
@@ -202,4 +254,4 @@ def main(root_dir=Path.cwd(), process_python=True, verbose=False):
if __name__ == "__main__":
main(process_python=True)
main(process_python=True, process_bash=True)
+27 -23
View File
@@ -6,6 +6,7 @@ from unittest.mock import mock_open, patch
from actions.update_markdown_code_blocks import (
add_indentation,
extract_code_blocks,
format_bash_with_prettier,
generate_temp_filename,
main,
process_markdown_file,
@@ -24,12 +25,20 @@ def test():
return True
```
And some bash code:
```bash
echo "Hello World"
```
"""
code_blocks = extract_code_blocks(markdown_content)
assert len(code_blocks["python"]) == 1
assert code_blocks["python"][0][1] == "def test():\n return True"
assert len(code_blocks["bash"]) == 1
assert code_blocks["bash"][0][1] == 'echo "Hello World"'
def test_remove_indentation():
"""Test removing indentation from code blocks."""
@@ -63,11 +72,16 @@ def test_generate_temp_filename():
"""Test generating temporary filenames."""
file_path = Path("docs/guide.md")
filename = generate_temp_filename(file_path, 0)
filename = generate_temp_filename(file_path, 0, "python")
assert "guide_docs_p0_" in filename
assert filename.endswith(".py")
filename = generate_temp_filename(file_path, 1, "bash")
assert "guide_docs_b1_" in filename
assert filename.endswith(".sh")
@patch("pathlib.Path.read_text")
@patch("pathlib.Path.write_text")
@@ -94,6 +108,16 @@ def test():
mock_file.assert_called_once()
def test_format_bash_skips_when_no_shell_files(tmp_path):
"""Test bash formatter skips Prettier when no shell snippets were extracted."""
(tmp_path / "snippet.py").write_text("print('ok')", encoding="utf-8")
with patch("subprocess.run") as mock_run:
format_bash_with_prettier(tmp_path)
mock_run.assert_not_called()
def test_main_skips_symlinked_markdown(tmp_path):
"""Test Markdown formatter skips symlinks to avoid formatting the same content twice."""
target = tmp_path / "AGENTS.md"
@@ -101,34 +125,14 @@ def test_main_skips_symlinked_markdown(tmp_path):
(tmp_path / "CLAUDE.md").symlink_to(target)
with patch("actions.update_markdown_code_blocks.process_markdown_file", return_value=("", [])) as mock_process:
main(root_dir=tmp_path, process_python=False)
main(root_dir=tmp_path, process_python=False, process_bash=False)
mock_process.assert_called_once()
assert mock_process.call_args.args[0] == target
def test_main_ignores_bash_blocks(tmp_path):
"""Test Markdown formatting ignores shell fences because docs often use them for terminal output."""
markdown = tmp_path / "example.md"
markdown.write_text(
"""# Example
```bash
Firmware Version: 4.23.0 (release,app,extended context switch buffer)
```
""",
encoding="utf-8",
)
with patch("subprocess.run") as mock_run:
main(root_dir=tmp_path, process_python=False)
mock_run.assert_not_called()
assert markdown.read_text(encoding="utf-8").endswith("```\n")
def test_main_real_files():
"""Test main function on actual repository Markdown files."""
# Run main on current directory which contains README.md and other Markdown files
# This provides real-world test coverage of the entire pipeline
main(process_python=True, verbose=False)
main(process_python=True, process_bash=True, verbose=False)