Created
January 12, 2025 17:04
-
-
Save tos-kamiya/2b6047ac622294eb42efd879dd6796d6 to your computer and use it in GitHub Desktop.
Processes a Markdown file to execute Python code blocks and append the execution results
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import io | |
import traceback | |
import argparse | |
from typing import List, TextIO | |
def python_interpreter(code: str) -> str: | |
""" | |
Executes a given Python code string and captures the output, errors, and final state of variables. | |
Args: | |
code (str): Python code to execute. | |
Returns: | |
str: Captured execution result including standard output, errors, and final variables. | |
""" | |
result = [] # Store the execution results | |
# Capture standard output and standard error using StringIO | |
stdout_capture = io.StringIO() | |
stderr_capture = io.StringIO() | |
# Execution environment with a disabled input function | |
context = { | |
"input": lambda *args, **kwargs: (_ for _ in ()).throw( | |
RuntimeError("input() is disabled") | |
) | |
} | |
# Temporarily redirect standard output and standard error | |
sys_stdout_original = sys.stdout | |
sys_stderr_original = sys.stderr | |
sys.stdout = stdout_capture | |
sys.stderr = stderr_capture | |
try: | |
# Execute the given code | |
exec(code, {}, context) | |
except Exception: | |
# Capture any execution errors | |
result.append("Error occurred during execution:") | |
result.append(traceback.format_exc()) | |
finally: | |
# Restore original standard output and standard error | |
sys.stdout = sys_stdout_original | |
sys.stderr = sys_stderr_original | |
# Add captured standard output to the result | |
stdout_content = stdout_capture.getvalue() | |
if stdout_content: | |
result.append("Standard Output:") | |
result.append(stdout_content) | |
# Add captured standard error to the result | |
stderr_content = stderr_capture.getvalue() | |
if stderr_content: | |
result.append("Standard Error:") | |
result.append(stderr_content) | |
# Add the final state of variables after code execution | |
result.append("Variables after execution:") | |
variables = { | |
k: v for k, v in context.items() if not k.startswith("__") and k != "input" | |
} | |
for name, value in variables.items(): | |
result.append(f"{name} = {repr(value)}") | |
# Return the result as a single string | |
return "\n".join(result) | |
def parse_code_blocks(lines: List[str]) -> List[List[str]]: | |
""" | |
Parses a list of lines from a Markdown file and splits them into blocks, separating Python code blocks. | |
A Python code block starts with "```python" and ends with "```". | |
Args: | |
lines (List[str]): List of lines from a Markdown file. | |
Returns: | |
List[List[str]]: A list of blocks, where each block is a list of lines. | |
""" | |
result = [] # List to store the separated blocks | |
current_block = [] # Temporary storage for the current block | |
in_code_block = False # Flag to track whether we are inside a code block | |
for line in lines: | |
if in_code_block: | |
# If inside a code block, add the line to the current block | |
current_block.append(line) | |
if line.strip() == "```": | |
# End of code block | |
result.append(current_block) | |
current_block = [] | |
in_code_block = False | |
else: | |
# If outside a code block | |
if line.strip() == "```python": | |
# Start of a new code block | |
if current_block: | |
result.append(current_block) # Add the previous block | |
current_block = [line] | |
in_code_block = True | |
else: | |
# Add line to the current block | |
current_block.append(line) | |
# Add the final block (if any) to the result | |
if current_block: | |
result.append(current_block) | |
return result | |
def process_markdown_file(input_stream: TextIO, output_stream: TextIO) -> None: | |
""" | |
Processes a Markdown file to execute Python code blocks and append the execution results. | |
Args: | |
input_stream (TextIO): Input stream to read the Markdown content. | |
output_stream (TextIO): Output stream to write the processed Markdown content. | |
""" | |
# Read all lines from the input stream and remove trailing newlines | |
lines = [line.rstrip("\n") for line in input_stream] | |
# Parse the Markdown into blocks | |
parsed_blocks = parse_code_blocks(lines) | |
processed_blocks = [] | |
for i, block in enumerate(parsed_blocks): | |
processed_blocks.append(block) | |
# Process Python code blocks (blocks with indices 1, 3, 5, ...) | |
if i % 2 == 1: | |
# Extract the Python code (excluding the start and end markers) | |
python_code = "\n".join(block[1:-1]) | |
# Execute the Python code and capture the result | |
execution_result = python_interpreter(python_code) | |
# Append the result as a Markdown code block | |
result_block = ["```", "## EXECUTION RESULT"] | |
result_block.extend(execution_result.splitlines()) | |
result_block.append("```") | |
processed_blocks.append(result_block) | |
# Generate the final Markdown by joining all processed blocks | |
output_lines = [] | |
for block in processed_blocks: | |
output_lines.extend(block) | |
output_lines.append("") # Add a blank line between blocks | |
# Write the processed Markdown to the output stream | |
output_stream.write("\n".join(output_lines) + "\n") | |
if __name__ == "__main__": | |
# Use argparse to handle command-line arguments | |
parser = argparse.ArgumentParser( | |
description="Process a Markdown file with Python code blocks." | |
) | |
parser.add_argument("input", help="Input Markdown file ('-' for standard input).") | |
parser.add_argument( | |
"-o", "--output", help="Output Markdown file (default: standard output)." | |
) | |
args = parser.parse_args() | |
# Determine the input stream (file or standard input) | |
if args.input == "-": | |
input_stream = sys.stdin | |
else: | |
input_stream = open(args.input, "r", encoding="utf-8") | |
# Determine the output stream (file or standard output) | |
if args.output: | |
output_stream = open(args.output, "w", encoding="utf-8") | |
else: | |
output_stream = sys.stdout | |
# Process the Markdown file | |
try: | |
process_markdown_file(input_stream, output_stream) | |
finally: | |
# Close the input/output streams if necessary | |
if args.input != "-": | |
input_stream.close() | |
if args.output: | |
output_stream.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment