Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 8e7ecf8

Browse files
committed
more work on running legacy docs
1 parent b030c94 commit 8e7ecf8

File tree

2 files changed

+154
-171
lines changed

2 files changed

+154
-171
lines changed

‎Makefile‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ RUN = uv run
44
PACKAGE_DIRS = _plotly_utils plotly
55
CODE_DIRS = ${PACKAGE_DIRS} scripts
66
EXAMPLE_SRC = $(wildcard doc/python/*.md)
7-
EXAMPLE_DST = $(patsubst doc/python/%.md,pages/examples/%.md,${EXAMPLE_SRC})
87

98
## commands: show available commands
109
commands:
@@ -23,11 +22,9 @@ docs-lint:
2322
docs-tmp:
2423
MKDOCS_TEMP_DIR=./docs_tmp ${RUN} mkdocs build
2524

26-
## examples: temporary target to copy and run doc/python
27-
examples: ${EXAMPLE_DST}
28-
29-
pages/examples/%.md: doc/python/%.md
30-
${RUN} bin/run_markdown.py --output $@ $<
25+
## examples: generate Markdown from doc/python
26+
examples:
27+
${RUN} bin/run_markdown.py --outdir pages/examples --inline --verbose ${EXAMPLE_SRC}
3128

3229
## format: reformat code
3330
format:

‎bin/run_markdown.py‎

100755100644
Lines changed: 151 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -8,131 +8,89 @@
88
from contextlib import redirect_stdout, redirect_stderr
99
import io
1010
from pathlib import Path
11+
import plotly.graph_objects as go
1112
import sys
1213
import traceback
1314

1415

15-
def parse_markdown(content):
16-
"""Parse markdown content and extract Python code blocks."""
17-
lines = content.split("\n")
18-
blocks = []
19-
current_block = None
20-
in_code_block = False
21-
22-
for i, line in enumerate(lines):
23-
# Start of Python code block
24-
if line.strip().startswith("```python"):
25-
in_code_block = True
26-
current_block = {
27-
"start_line": i,
28-
"end_line": None,
29-
"code": [],
30-
"type": "python",
31-
}
32-
33-
# End of code block
34-
elif line.strip() == "```" and in_code_block:
35-
in_code_block = False
36-
current_block["end_line"] = i
37-
current_block["code"] = "\n".join(current_block["code"])
38-
blocks.append(current_block)
39-
current_block = None
40-
41-
# Line inside code block
42-
elif in_code_block:
43-
current_block["code"].append(line)
16+
def main():
17+
args = _parse_args()
18+
for filename in args.input:
19+
_do_file(args, Path(filename))
4420

45-
return blocks
4621

22+
def _do_file(args, input_file):
23+
"""Process a single file."""
4724

48-
def execute_python_code(code, output_dir, output_figure_stem):
49-
"""Execute Python code and capture output and generated files."""
50-
# Capture stdout and stderr
51-
stdout_buffer = io.StringIO()
52-
stderr_buffer = io.StringIO()
25+
# Validate input file
26+
if not input_file.exists():
27+
print(f"Error: '{input_file}' not found", file=sys.stderr)
28+
sys.exit(1)
5329

54-
# Track files created during execution
55-
output_path = Path(output_dir)
56-
if not output_path.exists():
57-
output_path.mkdir(parents=True, exist_ok=True)
30+
# Determine output file path etc.
31+
stem = input_file.stem
32+
output_file = args.outdir / f"{input_file.stem}{input_file.suffix}"
33+
if input_file.resolve() == output_file.resolve():
34+
print(f"Error: output would overwrite input '{input_file}'", file=sys.stderr)
35+
sys.exit(1)
5836

59-
files_before = set(f.name for f in output_path.iterdir())
60-
result = {"stdout": "", "stderr": "", "error": None, "images": [], "html_files": []}
61-
figures = []
37+
# Read input
6238
try:
63-
# Create a custom show function to capture plotly figures
64-
def capture_plotly_show(fig):
65-
"""Custom show function that saves plotly figures instead of displaying them."""
66-
nonlocal figures
67-
figures.append(fig)
68-
png_filename = (
69-
f"{output_figure_stem}_{len(figures)}.png"
70-
)
71-
png_path = Path(output_dir) / png_filename
72-
fig.write_image(png_path, width=800, height=600)
73-
result["images"].append(png_filename)
74-
print(f"Plotly figure saved as PNG: {png_filename}")
75-
return
76-
77-
# Create a namespace for code execution
78-
exec_globals = {
79-
"__name__": "__main__",
80-
"__file__": "<markdown_code>",
81-
}
82-
83-
# Monkey patch plotly show method to capture figures
84-
original_show = None
85-
86-
# Execute the code with output capture
87-
with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
88-
# Try to import plotly and patch the show method
89-
def patched_show(self, *args, **kwargs):
90-
capture_plotly_show(self)
91-
import plotly.graph_objects as go
92-
original_show = go.Figure.show
93-
go.Figure.show = patched_show
39+
with open(input_file, "r", encoding="utf-8") as f:
40+
content = f.read()
41+
except Exception as e:
42+
print(f"Error reading input file: {e}", file=sys.stderr)
43+
sys.exit(1)
9444

95-
# Execute the code
96-
exec(code, exec_globals)
45+
# Parse markdown and extract code blocks
46+
_report(args.verbose, f"Processing {input_file}...")
47+
code_blocks = _parse_md(content)
48+
_report(args.verbose, f"- Found {len(code_blocks)} code blocks")
9749

98-
# Try to find and handle any plotly figures that were created and not already processed
99-
for name, obj in exec_globals.items():
100-
if (
101-
hasattr(obj, "__class__")
102-
and "plotly" in str(type(obj)).lower()
103-
and hasattr(obj, "show")
104-
):
105-
# This looks like a plotly figure that wasn't already processed by show()
106-
if obj not in figures:
107-
print("NOT ALREADY PROCESSED", obj, file=sys.stderr)
108-
capture_plotly_show(obj)
109-
110-
# Restore original show method if we patched it
111-
if original_show:
112-
import plotly.graph_objects as go
113-
go.Figure.show = original_show
50+
# Execute code blocks and collect results
51+
execution_results = []
52+
figure_counter = 0
53+
for i, block in enumerate(code_blocks):
54+
_report(args.verbose, f"- Executing block {i + 1}/{len(code_blocks)}")
55+
figure_counter, result = _run_code(block["code"], args.outdir, stem, figure_counter)
56+
execution_results.append(result)
57+
_report(result["error"], f" - Warning: block {i + 1} had an error")
58+
_report(result["images"], f" - Generated {len(result['images'])} image(s)")
11459

60+
# Generate and save output
61+
content = _generate_markdown(args, content, code_blocks, execution_results, args.outdir)
62+
try:
63+
with open(output_file, "w", encoding="utf-8") as f:
64+
f.write(content)
65+
_report(args.verbose, f"- Output written to {output_file}")
66+
_report(any(result["images"] for result in execution_results), f"- Images saved to {args.outdir}")
11567
except Exception as e:
116-
result["error"] = f"Error executing code: {str(e)}\n{traceback.format_exc()}"
68+
print(f"Error writing output file: {e}", file=sys.stderr)
69+
sys.exit(1)
11770

118-
result["stdout"] = stdout_buffer.getvalue()
119-
result["stderr"] = stderr_buffer.getvalue()
12071

121-
# Check for any additional files created
122-
output_path = Path(output_dir)
123-
if output_path.exists():
124-
files_after = set(f.name for f in output_path.iterdir())
125-
for f in (files_after - files_before):
126-
if f not in result["images"] and file.lower().endswith(".png"):
127-
result["images"].append(f)
72+
def _capture_plotly_show(fig, counter, result, output_dir, stem):
73+
"""Saves figures instead of displaying them."""
74+
print(f"CAPTURE SHOW counter is {counter}")
12875

129-
return result
76+
# Save PNG
77+
png_filename = f"{stem}_{counter}.png"
78+
png_path = output_dir / png_filename
79+
fig.write_image(png_path, width=800, height=600)
80+
result["images"].append(png_filename)
13081

82+
# Save HTML and get the content for embedding
83+
html_filename = f"{stem}_{counter}.html"
84+
html_path = output_dir / html_filename
85+
fig.write_html(html_path, include_plotlyjs="cdn")
86+
html_content = fig.to_html(include_plotlyjs="cdn", div_id=f"plotly-div-{counter}", full_html=False)
87+
result["html_files"].append(html_filename)
88+
result.setdefault("html_content", []).append(html_content)
13189

132-
def generate_output_markdown(content, code_blocks, execution_results, output_dir):
90+
91+
def _generate_markdown(args, content, code_blocks, execution_results, output_dir):
13392
"""Generate the output markdown with embedded results."""
13493
lines = content.split("\n")
135-
output_lines = []
13694

13795
# Sort code blocks by start line in reverse order for safe insertion
13896
sorted_blocks = sorted(
@@ -173,10 +131,13 @@ def generate_output_markdown(content, code_blocks, execution_results, output_dir
173131
insert_lines.append("")
174132
insert_lines.append(f"![Generated Plot](./{image})")
175133

176-
# Add HTML files (for plotly figures)
177-
for html_file in result.get("html_files", []):
178-
insert_lines.append("")
179-
insert_lines.append(f"[Interactive Plot](./{html_file})")
134+
# Embed HTML content for plotly figures
135+
if args.inline:
136+
for html_content in result.get("html_content", []):
137+
insert_lines.append("")
138+
insert_lines.append("**Interactive Plot:**")
139+
insert_lines.append("")
140+
insert_lines.extend(html_content.split("\n"))
180141

181142
# Insert the results after the code block
182143
if insert_lines:
@@ -187,75 +148,100 @@ def generate_output_markdown(content, code_blocks, execution_results, output_dir
187148
return "\n".join(lines)
188149

189150

190-
def main():
191-
parser = argparse.ArgumentParser(
192-
description="Process Markdown files with Python code blocks and generate output with results"
193-
)
194-
parser.add_argument("input_file", help="Input Markdown file")
195-
parser.add_argument(
196-
"-o", "--output", help="Output Markdown file (default: input_output.md)"
197-
)
198-
args = parser.parse_args()
151+
def _parse_args():
152+
"""Parse command-line arguments."""
153+
parser = argparse.ArgumentParser(description="Process Markdown files with code blocks")
154+
parser.add_argument("input", nargs="+", help="Input .md file")
155+
parser.add_argument("--inline", action="store_true", help="Inline HTML in .md")
156+
parser.add_argument("--outdir", type=Path, help="Output directory")
157+
parser.add_argument("--verbose", action="store_true", help="Report progress")
158+
return parser.parse_args()
199159

200-
# Validate input file
201-
if not Path(args.input_file).exists():
202-
print(f"Error: Input file '{args.input_file}' not found", file=sys.stderr)
203-
sys.exit(1)
204160

205-
# Determine output file path
206-
if args.output:
207-
output_file = args.output
208-
else:
209-
input_path = Path(args.input_file)
210-
output_file = str(
211-
input_path.parent / f"{input_path.stem}_output{input_path.suffix}"
212-
)
161+
def _parse_md(content):
162+
"""Parse Markdown and extract Python code blocks."""
163+
lines = content.split("\n")
164+
blocks = []
165+
current_block = None
166+
in_code_block = False
213167

214-
# Determine output directory for images
215-
output_dir = str(Path(output_file).parent)
168+
for i, line in enumerate(lines):
169+
# Start of Python code block
170+
if line.strip().startswith("```python"):
171+
in_code_block = True
172+
current_block = {
173+
"start_line": i,
174+
"end_line": None,
175+
"code": [],
176+
"type": "python",
177+
}
216178

217-
# Read input file
218-
try:
219-
withopen(args.input_file, "r", encoding="utf-8") asf:
220-
content = f.read()
221-
exceptExceptionase:
222-
print(f"Error reading input file: {e}", file=sys.stderr)
223-
sys.exit(1)
179+
# End of code block
180+
elifline.strip() =="```"andin_code_block:
181+
in_code_block=False
182+
current_block["end_line"] = i
183+
current_block["code"] ="\n".join(current_block["code"])
184+
blocks.append(current_block)
185+
current_block=None
224186

225-
print(f"Processing {args.input_file}...")
226-
output_figure_stem = Path(output_file).stem
187+
# Line inside code block
188+
elif in_code_block:
189+
current_block["code"].append(line)
227190

228-
# Parse markdown and extract code blocks
229-
code_blocks = parse_markdown(content)
230-
print(f"Found {len(code_blocks)} Python code blocks")
191+
return blocks
231192

232-
# Execute code blocks and collect results
233-
execution_results = []
234-
for i, block in enumerate(code_blocks):
235-
print(f"Executing code block {i + 1}/{len(code_blocks)}...")
236-
result = execute_python_code(block["code"], output_dir, output_figure_stem)
237-
execution_results.append(result)
238193

239-
ifresult["error"]:
240-
print(f" Warning: Code block {i+1} had an error")
241-
if result["images"]:
242-
print(f" Generated {len(result['images'])} image(s)")
194+
def_report(condition, message):
195+
"""Report if condition is true."""
196+
if condition:
197+
print(message, file=sys.stderr)
243198

244-
# Generate output markdown
245-
output_content = generate_output_markdown(
246-
content, code_blocks, execution_results, output_dir
247-
)
248199

249-
# Write output file
200+
def _run_code(code, output_dir, stem, figure_counter):
201+
"""Execute code capturing output and generated files."""
202+
# Capture stdout and stderr
203+
stdout_buffer = io.StringIO()
204+
stderr_buffer = io.StringIO()
205+
206+
# Track files created during execution
207+
if not output_dir.exists():
208+
output_dir.mkdir(parents=True, exist_ok=True)
209+
210+
files_before = set(f.name for f in output_dir.iterdir())
211+
result = {"stdout": "", "stderr": "", "error": None, "images": [], "html_files": []}
250212
try:
251-
with open(output_file, "w", encoding="utf-8") as f:
252-
f.write(output_content)
253-
print(f"Output written to {output_file}")
254-
if any(result["images"] for result in execution_results):
255-
print(f"Images saved to {output_dir}")
213+
214+
# Create a namespace for code execution
215+
exec_globals = {
216+
"__name__": "__main__",
217+
"__file__": "<markdown_code>",
218+
}
219+
220+
# Execute the code with output capture
221+
with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
222+
# Try to import plotly and patch the show method
223+
def patched_show(self, *args, **kwargs):
224+
nonlocal figure_counter
225+
figure_counter += 1
226+
_capture_plotly_show(self, figure_counter, result, output_dir, stem)
227+
original_show = go.Figure.show
228+
go.Figure.show = patched_show
229+
exec(code, exec_globals)
230+
go.Figure.show = original_show
231+
256232
except Exception as e:
257-
print(f"Error writing output file: {e}", file=sys.stderr)
258-
sys.exit(1)
233+
result["error"] = f"Error executing code: {str(e)}\n{traceback.format_exc()}"
234+
235+
result["stdout"] = stdout_buffer.getvalue()
236+
result["stderr"] = stderr_buffer.getvalue()
237+
238+
# Check for any additional files created
239+
files_after = set(f.name for f in output_dir.iterdir())
240+
for f in (files_after - files_before):
241+
if f not in result["images"] and f.lower().endswith(".png"):
242+
result["images"].append(f)
243+
244+
return figure_counter, result
259245

260246

261247
if __name__ == "__main__":

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /