Skip to content

Instantly share code, notes, and snippets.

@originalankur
Created September 25, 2025 15:30
Show Gist options
  • Save originalankur/fbc29274b964cd27193186fe7939ced5 to your computer and use it in GitHub Desktop.
Save originalankur/fbc29274b964cd27193186fe7939ced5 to your computer and use it in GitHub Desktop.
AsciiDoc Python Code Extractor and Executor - reads AsciiDoc files, extracts Python code blocks, and executes them interactively with user confirmation.
import re
import os
import sys
import tempfile
import subprocess
from pathlib import Path
from typing import List, Tuple
class AsciiDocCodeExtractor:
def __init__(self, file_path: str):
self.file_path = Path(file_path)
self.code_blocks = []
def extract_python_code_blocks(self) -> List[Tuple[str, int]]:
"""Extract Python code blocks from AsciiDoc file."""
if not self.file_path.exists():
print(f"Error: File {self.file_path} not found")
return []
try:
with open(self.file_path, 'r', encoding='utf-8') as file:
content = file.read()
except Exception as e:
print(f"Error reading file: {e}")
return []
# Pattern to match Python code blocks in AsciiDoc
# Matches both [source,python] and [source, python] formats
patterns = [
r'\[source,\s*python\]\s*\n----\n(.*?)\n----',
r'\[source,\s*py\]\s*\n----\n(.*?)\n----',
r'```python\n(.*?)\n```', # Also support markdown-style blocks
r'```py\n(.*?)\n```'
]
code_blocks = []
for pattern in patterns:
matches = re.finditer(pattern, content, re.DOTALL | re.IGNORECASE)
for match in matches:
code = match.group(1).strip()
# Only add non-empty code blocks
if code:
# Find line number
line_num = content[:match.start()].count('\n') + 1
code_blocks.append((code, line_num))
# Sort by line number to maintain order
code_blocks.sort(key=lambda x: x[1])
return code_blocks
def display_code_block(self, code: str, block_num: int, line_num: int):
"""Display a code block with formatting."""
print(f"\n{'='*60}")
print(f"Code Block #{block_num} (Line {line_num})")
print(f"{'='*60}")
print(code)
print(f"{'='*60}")
def execute_code_block(self, code: str, block_num: int) -> bool:
"""Execute a Python code block and return success status."""
try:
# Create a temporary file for the code
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as temp_file:
temp_file.write(code)
temp_file_path = temp_file.name
print(f"\nExecuting code block #{block_num}...")
print("-" * 40)
# Execute the Python file
result = subprocess.run(
[sys.executable, temp_file_path],
capture_output=True,
text=True,
timeout=30 # 30 second timeout to ensure CI/CD doesn't block ( Talk to Ankur before removing. )
)
# Display output
if result.stdout:
print("STDOUT:")
print(result.stdout)
if result.stderr:
print("STDERR:")
print(result.stderr)
if result.returncode == 0:
print(f"✅ Code block #{block_num} executed successfully")
return True
else:
print(f"❌ Code block #{block_num} failed with return code {result.returncode}")
return False
except subprocess.TimeoutExpired:
print(f"⏰ Code block #{block_num} timed out after 30 seconds")
return False
except Exception as e:
print(f"❌ Error executing code block #{block_num}: {e}")
return False
finally:
# Clean up temporary file
try:
os.unlink(temp_file_path)
except:
pass
def get_user_choice(self) -> str:
"""Get user choice for code execution."""
while True:
choice = input("\nChoose an option:\n"
" [y] Execute this code block\n"
" [n] Skip this code block\n"
" [a] Execute all remaining blocks\n"
" [q] Quit\n"
"Enter choice: ").lower().strip()
if choice in ['y', 'n', 'a', 'q']:
return choice
print("Invalid choice. Please enter 'y', 'n', 'a', or 'q'.")
def run_interactive_execution(self):
"""Main interactive execution loop."""
print(f"Extracting Python code blocks from: {self.file_path}")
code_blocks = self.extract_python_code_blocks()
if not code_blocks:
print("No Python code blocks found in the file.")
return
print(f"Found {len(code_blocks)} Python code block(s)")
execute_all = False
executed_count = 0
success_count = 0
for i, (code, line_num) in enumerate(code_blocks, 1):
self.display_code_block(code, i, line_num)
if not execute_all:
choice = self.get_user_choice()
if choice == 'q':
print("Exiting...")
break
elif choice == 'n':
print(f"Skipping code block #{i}")
continue
elif choice == 'a':
execute_all = True
print("Executing all remaining code blocks...")
# 'y' falls through to execution
# Execute the code block
executed_count += 1
if self.execute_code_block(code, i):
success_count += 1
# Summary
print(f"\n{'='*60}")
print("EXECUTION SUMMARY")
print(f"{'='*60}")
print(f"Total code blocks found: {len(code_blocks)}")
print(f"Code blocks executed: {executed_count}")
print(f"Successful executions: {success_count}")
print(f"Failed executions: {executed_count - success_count}")
def main():
"""Main function to handle command line arguments and run the extractor."""
if len(sys.argv) != 2:
print("Usage: python asciidoc_code_executor.py <asciidoc_file>")
print("\nExample:")
print(" python asciidoc_code_executor.py document.adoc")
sys.exit(1)
file_path = sys.argv[1]
if not os.path.exists(file_path):
print(f"Error: File '{file_path}' not found")
sys.exit(1)
extractor = AsciiDocCodeExtractor(file_path)
try:
extractor.run_interactive_execution()
except KeyboardInterrupt:
print("\n\nInterrupted by user. Exiting...")
sys.exit(0)
except Exception as e:
print(f"Unexpected error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment