chore: import upstream snapshot with attribution
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
This commit is contained in:
Executable
+159
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Update README.md with live code snippets from example files.
|
||||
|
||||
This script finds specially marked code blocks in README.md and updates them
|
||||
with the actual code from the referenced files.
|
||||
|
||||
Usage:
|
||||
python scripts/update_readme_snippets.py
|
||||
python scripts/update_readme_snippets.py --check # Check mode for CI
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_github_url(file_path: str) -> str:
|
||||
"""Generate a GitHub URL for the file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file relative to repo root
|
||||
|
||||
Returns:
|
||||
GitHub URL
|
||||
"""
|
||||
base_url = "https://github.com/modelcontextprotocol/python-sdk/blob/main"
|
||||
return f"{base_url}/{file_path}"
|
||||
|
||||
|
||||
def process_snippet_block(match: re.Match[str], check_mode: bool = False) -> str:
|
||||
"""Process a single snippet-source block.
|
||||
|
||||
Args:
|
||||
match: The regex match object
|
||||
check_mode: If True, return original if no changes needed
|
||||
|
||||
Returns:
|
||||
The updated block content
|
||||
"""
|
||||
full_match = match.group(0)
|
||||
indent = match.group(1)
|
||||
file_path = match.group(2)
|
||||
|
||||
try:
|
||||
# Read the entire file. A missing source file must be fatal: a "Warning"
|
||||
# that returns the stale block lets --check pass with exit 0, so a
|
||||
# renamed or deleted snippet is invisible to CI. SystemExit deliberately
|
||||
# escapes the `except Exception` below.
|
||||
file = Path(file_path)
|
||||
if not file.exists():
|
||||
sys.exit(f"Error: snippet-source file not found: {file_path}")
|
||||
|
||||
code = file.read_text().rstrip()
|
||||
github_url = get_github_url(file_path)
|
||||
|
||||
# Build the replacement block
|
||||
indented_code = code.replace("\n", f"\n{indent}")
|
||||
replacement = f"""{indent}<!-- snippet-source {file_path} -->
|
||||
{indent}```python
|
||||
{indent}{indented_code}
|
||||
{indent}```
|
||||
|
||||
{indent}_Full example: [{file_path}]({github_url})_
|
||||
{indent}<!-- /snippet-source -->"""
|
||||
|
||||
# In check mode, only check if code has changed
|
||||
if check_mode:
|
||||
# Extract existing code from the match
|
||||
existing_content = match.group(3)
|
||||
if existing_content is not None:
|
||||
existing_lines = existing_content.strip().split("\n")
|
||||
# Find code between ```python and ```
|
||||
code_lines: list[str] = []
|
||||
in_code = False
|
||||
for line in existing_lines:
|
||||
if line.strip() == "```python":
|
||||
in_code = True
|
||||
elif line.strip() == "```":
|
||||
break
|
||||
elif in_code:
|
||||
code_lines.append(line)
|
||||
existing_code = "\n".join(code_lines).strip()
|
||||
# Compare with the indented version we would generate
|
||||
expected_code = code.replace("\n", f"\n{indent}").strip()
|
||||
if existing_code == expected_code:
|
||||
return full_match
|
||||
|
||||
return replacement
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing {file_path}: {e}")
|
||||
return full_match
|
||||
|
||||
|
||||
def update_readme_snippets(check_mode: bool = False) -> bool:
|
||||
"""Update code snippets in README.md with live code from source files.
|
||||
|
||||
Args:
|
||||
check_mode: If True, only check if updates are needed without modifying
|
||||
|
||||
Returns:
|
||||
True if file is up to date or was updated, False if check failed
|
||||
"""
|
||||
readme_path = Path("README.md")
|
||||
if not readme_path.exists():
|
||||
print(f"Error: README file not found: {readme_path}")
|
||||
return False
|
||||
|
||||
content = readme_path.read_text()
|
||||
original_content = content
|
||||
|
||||
# Pattern to match snippet-source blocks
|
||||
# Matches: <!-- snippet-source path/to/file.py -->
|
||||
# ... any content ...
|
||||
# <!-- /snippet-source -->
|
||||
pattern = r"^(\s*)<!-- snippet-source ([^\s]+) -->\n" r"(.*?)" r"^\1<!-- /snippet-source -->"
|
||||
|
||||
# Process all snippet-source blocks
|
||||
updated_content = re.sub(
|
||||
pattern, lambda m: process_snippet_block(m, check_mode), content, flags=re.MULTILINE | re.DOTALL
|
||||
)
|
||||
|
||||
if check_mode:
|
||||
if updated_content != original_content:
|
||||
print(
|
||||
f"Error: {readme_path} has outdated code snippets. "
|
||||
"Run 'python scripts/update_readme_snippets.py' to update."
|
||||
)
|
||||
return False
|
||||
else:
|
||||
print(f"✓ {readme_path} code snippets are up to date")
|
||||
return True
|
||||
else:
|
||||
if updated_content != original_content:
|
||||
readme_path.write_text(updated_content)
|
||||
print(f"✓ Updated {readme_path}")
|
||||
else:
|
||||
print(f"✓ {readme_path} already up to date")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
parser = argparse.ArgumentParser(description="Update README code snippets from source files")
|
||||
parser.add_argument(
|
||||
"--check", action="store_true", help="Check mode - verify snippets are up to date without modifying"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
success = update_readme_snippets(check_mode=args.check)
|
||||
|
||||
if not success:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user