Skip to content

Commit ea1824e

Browse files
blankjulclaude
andcommitted
Update documentation build system and improve theme customization
- Split documentation CLI into separate compile, run, and build commands - Add individual notebook execution with failure handling (deletes failed notebooks) - Clean up theme customization: remove complex sidebar overrides - Hide right sidebar content while preserving layout spacing - Hide theme toggle button for consistent light theme - Simplify JavaScript to only essential ID sanitization - Improve mobile responsiveness and content alignment 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8918110 commit ea1824e

File tree

5 files changed

+152
-388
lines changed

5 files changed

+152
-388
lines changed

docs/cli.py

Lines changed: 108 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -79,51 +79,97 @@ def install_pymoo():
7979
return False
8080

8181

82-
def smart_build(force=False):
83-
"""Smart build: find missing notebooks, convert and execute in batches, then build."""
84-
print("🚀 Starting smart documentation build...")
82+
def compile_notebooks(force=False, files=None):
83+
"""Convert .md files to .ipynb notebooks."""
84+
print("📝 Starting notebook compilation...")
8585

86-
# Find all .md files recursively
87-
all_md_files = glob.glob("source/**/*.md", recursive=True)
88-
89-
if force:
90-
# Process all .md files when force is enabled
91-
md_to_process = all_md_files
92-
print(f"🔄 Force mode: Processing all {len(md_to_process)} markdown files")
86+
if files:
87+
# Process specific files provided as arguments
88+
md_to_process = files
89+
print(f"📋 Processing {len(md_to_process)} specified files")
9390
else:
94-
# Only process .md files that don't have corresponding .ipynb files
95-
md_to_process = []
96-
for md_file in all_md_files:
97-
nb_file = Path(md_file).with_suffix('.ipynb')
98-
if not nb_file.exists():
99-
md_to_process.append(md_file)
91+
# Find all .md files recursively
92+
all_md_files = glob.glob("source/**/*.md", recursive=True)
10093

101-
print(f"📋 Found {len(md_to_process)} markdown files without notebooks (out of {len(all_md_files)} total)")
94+
if force:
95+
# Process all .md files when force is enabled
96+
md_to_process = all_md_files
97+
print(f"🔄 Force mode: Processing all {len(md_to_process)} markdown files")
98+
else:
99+
# Only process .md files that don't have corresponding .ipynb files
100+
md_to_process = []
101+
for md_file in all_md_files:
102+
nb_file = Path(md_file).with_suffix('.ipynb')
103+
if not nb_file.exists():
104+
md_to_process.append(md_file)
105+
106+
print(f"📋 Found {len(md_to_process)} markdown files without notebooks (out of {len(all_md_files)} total)")
102107

103108
if not md_to_process:
104109
print("✅ All notebooks already exist. Use --force to regenerate all.")
110+
return
111+
112+
# Convert all needed .md files to .ipynb in one batch
113+
print(f"📝 Converting {len(md_to_process)} markdown files to notebooks...")
114+
run_command([
115+
"python", "-m", "jupytext",
116+
"--to", "notebook"
117+
] + md_to_process)
118+
print("✅ Notebook compilation completed")
119+
120+
121+
def run_notebooks(force=False, files=None):
122+
"""Execute .ipynb notebooks, deleting those that fail."""
123+
print("⚡ Starting notebook execution...")
124+
125+
if files:
126+
# Process specific files provided as arguments
127+
nb_files = [str(Path(f).with_suffix('.ipynb')) for f in files]
128+
print(f"📋 Processing {len(nb_files)} specified notebooks")
105129
else:
106-
# Step 1: Convert all needed .md files to .ipynb in one batch
107-
print(f"📝 Converting {len(md_to_process)} markdown files to notebooks...")
108-
run_command([
109-
"python", "-m", "jupytext",
110-
"--to", "notebook"
111-
] + md_to_process)
112-
print("✅ Notebook conversion completed")
130+
# Find all .ipynb files that have corresponding .md files
131+
all_md_files = glob.glob("source/**/*.md", recursive=True)
132+
all_nb_files = [str(Path(md_file).with_suffix('.ipynb')) for md_file in all_md_files]
113133

114-
# Step 2: Execute all newly created notebooks in one batch
115-
nb_files = [str(Path(md_file).with_suffix('.ipynb')) for md_file in md_to_process]
116-
print(f"⚡ Executing {len(nb_files)} notebooks...")
117-
run_command([
134+
if force:
135+
# Process all notebooks when force is enabled
136+
nb_files = [nb for nb in all_nb_files if Path(nb).exists()]
137+
print(f"🔄 Force mode: Processing all {len(nb_files)} existing notebooks")
138+
else:
139+
# Only process notebooks that exist (skip missing ones)
140+
nb_files = [nb for nb in all_nb_files if Path(nb).exists()]
141+
print(f"📋 Found {len(nb_files)} existing notebooks to execute")
142+
143+
if not nb_files:
144+
print("✅ No notebooks found to execute.")
145+
return
146+
147+
# Execute notebooks individually and delete if execution fails
148+
print(f"⚡ Executing {len(nb_files)} notebooks individually...")
149+
150+
failed_files = []
151+
for nb_file in nb_files:
152+
print(f" Executing: {nb_file}")
153+
result = run_command([
118154
"python", "-m", "jupyter", "nbconvert",
119-
"--execute", "--inplace"
120-
] + nb_files)
121-
print("✅ Notebook execution completed")
155+
"--execute", "--inplace", nb_file
156+
], check=False)
157+
158+
if result.returncode != 0:
159+
print(f" ❌ Execution failed for {nb_file} - deleting notebook")
160+
Path(nb_file).unlink(missing_ok=True)
161+
failed_files.append(nb_file)
162+
else:
163+
print(f" ✅ Successfully executed {nb_file}")
122164

123-
# Step 3: Build HTML documentation
124-
print("🏗️ Building HTML documentation...")
125-
build_html()
126-
print("🎉 Smart build completed!")
165+
if failed_files:
166+
print(f"⚠️ {len(failed_files)} notebooks failed execution and were deleted:")
167+
for failed_file in failed_files:
168+
print(f" - {failed_file}")
169+
print(" These will be recompiled on next compile run.")
170+
171+
successful_count = len(nb_files) - len(failed_files)
172+
print(f"✅ Notebook execution completed: {successful_count}/{len(nb_files)} successful")
127173

128174

129175
def build_html():
@@ -202,16 +248,18 @@ def main():
202248
epilog="""
203249
Examples:
204250
uvx pymoo-docs clean # Clean all generated files
205-
uvx pymoo-docs compile # Convert all .md to .ipynb
206-
uvx pymoo-docs compile --skip-existing # Only convert missing notebooks
251+
uvx pymoo-docs compile # Convert missing .md to .ipynb
207252
uvx pymoo-docs compile --force # Force regenerate all notebooks
208253
uvx pymoo-docs compile file1.md file2.md # Convert specific files
209254
uvx pymoo-docs compile algorithms/nsga2.md # Convert single file
255+
uvx pymoo-docs run # Execute existing notebooks
256+
uvx pymoo-docs run --force # Force re-execute all notebooks
257+
uvx pymoo-docs run file1.md file2.md # Execute specific notebooks
210258
uvx pymoo-docs build # Build HTML documentation
211259
uvx pymoo-docs serve # Serve documentation locally
212260
uvx pymoo-docs check # Fast build for testing
213-
uvx pymoo-docs all # Clean, compile, and build
214-
uvx pymoo-docs all --skip-existing # Full build, skip existing notebooks
261+
uvx pymoo-docs all # Compile, run, and build
262+
uvx pymoo-docs all --force # Force full rebuild
215263
"""
216264
)
217265

@@ -229,14 +277,22 @@ def main():
229277
help="Force compilation of all files"
230278
)
231279
compile_parser.add_argument(
232-
"--skip-existing",
280+
"files",
281+
nargs="*",
282+
help="Specific files to compile (optional)"
283+
)
284+
285+
# Run command
286+
run_parser = subparsers.add_parser("run", help="Execute .ipynb notebooks")
287+
run_parser.add_argument(
288+
"--force",
233289
action="store_true",
234-
help="Skip files that already have notebooks"
290+
help="Force execution of all notebooks"
235291
)
236-
compile_parser.add_argument(
292+
run_parser.add_argument(
237293
"files",
238294
nargs="*",
239-
help="Specific files to compile (optional)"
295+
help="Specific files to run (optional)"
240296
)
241297

242298
# Build command
@@ -255,16 +311,11 @@ def main():
255311
check_parser = subparsers.add_parser("check", help="Fast build for testing")
256312

257313
# All command
258-
all_parser = subparsers.add_parser("all", help="Clean, compile, and build")
259-
all_parser.add_argument(
260-
"--skip-existing",
261-
action="store_true",
262-
help="Skip files that already have notebooks"
263-
)
314+
all_parser = subparsers.add_parser("all", help="Compile, run, and build")
264315
all_parser.add_argument(
265316
"--force",
266317
action="store_true",
267-
help="Force compilation of all files"
318+
help="Force compilation and execution of all files"
268319
)
269320

270321
args = parser.parse_args()
@@ -300,24 +351,26 @@ def main():
300351
sys.exit(1)
301352

302353
# Install pymoo if needed for build commands
303-
if args.command in ["compile", "build", "check", "all"]:
354+
if args.command in ["compile", "run", "build", "check", "all"]:
304355
install_pymoo()
305356

306357
# Execute the requested command
307358
if args.command == "clean":
308359
clean_docs()
309360
elif args.command == "compile":
310-
# Legacy compile command - use smart_build with force if specified
311-
smart_build(force=args.force)
361+
compile_notebooks(force=args.force, files=args.files)
362+
elif args.command == "run":
363+
run_notebooks(force=args.force, files=args.files)
312364
elif args.command == "build":
313365
build_html()
314366
elif args.command == "serve":
315367
serve_docs(args.port)
316368
elif args.command == "check":
317369
check_mode()
318370
elif args.command == "all":
319-
clean_docs()
320-
smart_build(force=args.force)
371+
compile_notebooks(force=args.force)
372+
run_notebooks(force=args.force)
373+
build_html()
321374
print("🎉 Full documentation build completed!")
322375

323376

0 commit comments

Comments
 (0)