mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-04-29 17:20:21 -05:00
Simplify binder code by removing RICH availability checks
Since RICH is now a required dependency, removed all conditional logic:
- Removed try/except import block for rich modules
- Removed RICH_AVAILABLE variable and all associated checks
- Removed fallback print statements for when RICH is unavailable
- Simplified console initialization to always use Console()
- Removed ~70 conditional if statements throughout the code
Result: Cleaner, more maintainable code with the same beautiful output.
✅ Verified: All functionality working correctly after simplification
This commit is contained in:
473
binder
473
binder
@@ -13,22 +13,16 @@ import signal
|
||||
from pathlib import Path
|
||||
from contextlib import contextmanager
|
||||
|
||||
try:
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeElapsedColumn
|
||||
from rich.tree import Tree
|
||||
from rich.text import Text
|
||||
from rich.live import Live
|
||||
from rich import print as rprint
|
||||
RICH_AVAILABLE = True
|
||||
except ImportError:
|
||||
RICH_AVAILABLE = False
|
||||
def rprint(*args, **kwargs):
|
||||
print(*args, **kwargs)
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeElapsedColumn
|
||||
from rich.tree import Tree
|
||||
from rich.text import Text
|
||||
from rich.live import Live
|
||||
from rich import print as rprint
|
||||
|
||||
console = Console() if RICH_AVAILABLE else None
|
||||
console = Console()
|
||||
|
||||
class BookBinder:
|
||||
def __init__(self):
|
||||
@@ -41,10 +35,6 @@ class BookBinder:
|
||||
|
||||
def show_banner(self):
|
||||
"""Display beautiful banner"""
|
||||
if not RICH_AVAILABLE:
|
||||
print("📚 Book Binder - Self-Contained MLSysBook CLI")
|
||||
return
|
||||
|
||||
banner = Panel.fit(
|
||||
"[bold blue]📚 Book Binder[/bold blue]\n"
|
||||
"[dim]Self-contained lightning-fast MLSysBook development CLI[/dim]",
|
||||
@@ -127,12 +117,6 @@ class BookBinder:
|
||||
"""Display beautiful status information"""
|
||||
status = self.get_status()
|
||||
|
||||
if not RICH_AVAILABLE:
|
||||
print(f"📊 Status:")
|
||||
print(f" 🔗 Active config: {status['active_config']}")
|
||||
print(f" ✅ Clean: {status['is_clean']}")
|
||||
return
|
||||
|
||||
table = Table(title="📊 Current Status", show_header=False, box=None)
|
||||
table.add_column("", style="cyan", no_wrap=True)
|
||||
table.add_column("", style="white")
|
||||
@@ -150,14 +134,6 @@ class BookBinder:
|
||||
"""Display available chapters in a beautiful format"""
|
||||
chapters = self.find_chapters()
|
||||
|
||||
if not RICH_AVAILABLE:
|
||||
print("📚 Available chapters:")
|
||||
for chapter in chapters[:10]:
|
||||
print(f" {chapter}")
|
||||
if len(chapters) > 10:
|
||||
print(f" ... and {len(chapters) - 10} more")
|
||||
return
|
||||
|
||||
tree = Tree("📚 [bold blue]Available Chapters[/bold blue]")
|
||||
|
||||
# Group by category
|
||||
@@ -185,11 +161,6 @@ class BookBinder:
|
||||
@contextmanager
|
||||
def progress_context(self, description):
|
||||
"""Context manager for progress display"""
|
||||
if not RICH_AVAILABLE:
|
||||
print(f"Running: {description}")
|
||||
yield None
|
||||
return
|
||||
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
@@ -236,10 +207,7 @@ class BookBinder:
|
||||
)
|
||||
return result.returncode == 0
|
||||
except Exception as e:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[red]Error: {e}[/red]")
|
||||
else:
|
||||
print(f"Error: {e}")
|
||||
console.print(f"[red]Error: {e}[/red]")
|
||||
return False
|
||||
|
||||
def open_output_file(self, output_text):
|
||||
@@ -263,28 +231,18 @@ class BookBinder:
|
||||
full_path = Path(file_path)
|
||||
|
||||
if full_path.exists():
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[green]🌐 Opening in browser: {full_path.name}[/green]")
|
||||
else:
|
||||
print(f"🌐 Opening in browser: {full_path.name}")
|
||||
console.print(f"[green]🌐 Opening in browser: {full_path.name}[/green]")
|
||||
|
||||
try:
|
||||
# Use 'open' command on macOS
|
||||
subprocess.run(['open', str(full_path)], check=True)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow]⚠️ Could not open file automatically: {full_path}[/yellow]")
|
||||
else:
|
||||
print(f"⚠️ Could not open file automatically: {full_path}")
|
||||
console.print(f"[yellow]⚠️ Could not open file automatically: {full_path}[/yellow]")
|
||||
return False
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow]⚠️ Output file not found: {full_path}[/yellow]")
|
||||
console.print(f"[dim] Tried: {full_path}[/dim]")
|
||||
else:
|
||||
print(f"⚠️ Output file not found: {full_path}")
|
||||
print(f" Tried: {full_path}")
|
||||
console.print(f"[yellow]⚠️ Output file not found: {full_path}[/yellow]")
|
||||
console.print(f"[dim] Tried: {full_path}[/dim]")
|
||||
return False
|
||||
|
||||
# No "Output created:" line found
|
||||
@@ -299,26 +257,17 @@ class BookBinder:
|
||||
full_path = Path(file_path)
|
||||
|
||||
if full_path.exists():
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[green]🌐 Opening in browser: {full_path.name}[/green]")
|
||||
else:
|
||||
print(f"🌐 Opening in browser: {full_path.name}")
|
||||
console.print(f"[green]🌐 Opening in browser: {full_path.name}[/green]")
|
||||
|
||||
try:
|
||||
# Use 'open' command on macOS
|
||||
subprocess.run(['open', str(full_path)], check=True)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow]⚠️ Could not open file automatically: {full_path}[/yellow]")
|
||||
else:
|
||||
print(f"⚠️ Could not open file automatically: {full_path}")
|
||||
console.print(f"[yellow]⚠️ Could not open file automatically: {full_path}[/yellow]")
|
||||
return False
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow]⚠️ Output file not found: {full_path}[/yellow]")
|
||||
else:
|
||||
print(f"⚠️ Output file not found: {full_path}")
|
||||
console.print(f"[yellow]⚠️ Output file not found: {full_path}[/yellow]")
|
||||
return False
|
||||
|
||||
def set_fast_build_mode(self, config_file, target_path):
|
||||
@@ -353,10 +302,7 @@ class BookBinder:
|
||||
f'navigate: true\n{render_config}'
|
||||
)
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow]⚠️ Could not find project navigation section in {config_file}[/yellow]")
|
||||
else:
|
||||
print(f"⚠️ Could not find project navigation section in {config_file}")
|
||||
console.print(f"[yellow]⚠️ Could not find project navigation section in {config_file}[/yellow]")
|
||||
return False
|
||||
|
||||
elif 'type: book' in content:
|
||||
@@ -391,28 +337,19 @@ class BookBinder:
|
||||
new_lines.append(line)
|
||||
|
||||
if not target_found:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow]⚠️ Target chapter {target_relative_path} not found in chapters list[/yellow]")
|
||||
else:
|
||||
print(f"⚠️ Target chapter {target_relative_path} not found in chapters list")
|
||||
console.print(f"[yellow]⚠️ Target chapter {target_relative_path} not found in chapters list[/yellow]")
|
||||
return False
|
||||
|
||||
content = '\n'.join(new_lines)
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow]⚠️ Unknown project type in {config_file}[/yellow]")
|
||||
else:
|
||||
print(f"⚠️ Unknown project type in {config_file}")
|
||||
console.print(f"[yellow]⚠️ Unknown project type in {config_file}[/yellow]")
|
||||
return False
|
||||
|
||||
# Write modified config
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f" 📝 Fast build mode: Only rendering {target_stem} + essential files")
|
||||
else:
|
||||
print(f" 📝 Fast build mode: Only rendering {target_stem} + essential files")
|
||||
console.print(f" 📝 Fast build mode: Only rendering {target_stem} + essential files")
|
||||
|
||||
return True
|
||||
|
||||
@@ -456,10 +393,7 @@ class BookBinder:
|
||||
f'navigate: true\n{render_config}'
|
||||
)
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow]⚠️ Could not find project navigation section in {config_file}[/yellow]")
|
||||
else:
|
||||
print(f"⚠️ Could not find project navigation section in {config_file}")
|
||||
console.print(f"[yellow]⚠️ Could not find project navigation section in {config_file}[/yellow]")
|
||||
return False
|
||||
|
||||
elif 'type: book' in content:
|
||||
@@ -494,28 +428,19 @@ class BookBinder:
|
||||
new_lines.append(line)
|
||||
|
||||
if targets_found != len(target_relative_paths):
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow]⚠️ Only {targets_found}/{len(target_relative_paths)} target chapters found in chapters list[/yellow]")
|
||||
else:
|
||||
print(f"⚠️ Only {targets_found}/{len(target_relative_paths)} target chapters found in chapters list")
|
||||
console.print(f"[yellow]⚠️ Only {targets_found}/{len(target_relative_paths)} target chapters found in chapters list[/yellow]")
|
||||
return False
|
||||
|
||||
content = '\n'.join(new_lines)
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow]⚠️ Unknown project type in {config_file}[/yellow]")
|
||||
else:
|
||||
print(f"⚠️ Unknown project type in {config_file}")
|
||||
console.print(f"[yellow]⚠️ Unknown project type in {config_file}[/yellow]")
|
||||
return False
|
||||
|
||||
# Write modified config
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f" 📝 Fast build mode: Only rendering {', '.join(target_stems)} + essential files")
|
||||
else:
|
||||
print(f" 📝 Fast build mode: Only rendering {', '.join(target_stems)} + essential files")
|
||||
console.print(f" 📝 Fast build mode: Only rendering {', '.join(target_stems)} + essential files")
|
||||
|
||||
return True
|
||||
|
||||
@@ -526,15 +451,9 @@ class BookBinder:
|
||||
if backup_path.exists():
|
||||
shutil.copy2(backup_path, config_file)
|
||||
backup_path.unlink()
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f" 🔄 Restored from backup: {backup_path.name}")
|
||||
else:
|
||||
print(f" 🔄 Restored from backup: {backup_path.name}")
|
||||
console.print(f" 🔄 Restored from backup: {backup_path.name}")
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow]⚠️ No backup found: {backup_path}[/yellow]")
|
||||
else:
|
||||
print(f"⚠️ No backup found: {backup_path}")
|
||||
console.print(f"[yellow]⚠️ No backup found: {backup_path}[/yellow]")
|
||||
|
||||
def ensure_clean_config(self, config_file):
|
||||
"""Ensure config is in clean state (remove any render restrictions)"""
|
||||
@@ -544,10 +463,7 @@ class BookBinder:
|
||||
# Restore from backup if it exists
|
||||
shutil.copy2(backup_path, config_file)
|
||||
backup_path.unlink()
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f" ✅ {config_file.name} restored from backup")
|
||||
else:
|
||||
print(f" ✅ {config_file.name} restored from backup")
|
||||
console.print(f" ✅ {config_file.name} restored from backup")
|
||||
else:
|
||||
# Check if file has render restrictions and remove them
|
||||
with open(config_file, 'r', encoding='utf-8') as f:
|
||||
@@ -575,15 +491,9 @@ class BookBinder:
|
||||
if cleaned_content != content:
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
f.write(cleaned_content)
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f" ✅ {config_file.name} cleaned (removed render restrictions)")
|
||||
else:
|
||||
print(f" ✅ {config_file.name} cleaned (removed render restrictions)")
|
||||
console.print(f" ✅ {config_file.name} cleaned (removed render restrictions)")
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f" ✅ {config_file.name} already clean")
|
||||
else:
|
||||
print(f" ✅ {config_file.name} already clean")
|
||||
console.print(f" ✅ {config_file.name} already clean")
|
||||
|
||||
def setup_symlink(self, format_type):
|
||||
"""Setup _quarto.yml symlink"""
|
||||
@@ -613,12 +523,8 @@ class BookBinder:
|
||||
|
||||
def build_multiple(self, chapter_list, format_type="html"):
|
||||
"""Build multiple chapters together in a single unified build"""
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[green]🚀 Building {len(chapter_list)} chapters together[/green] [dim]({format_type})[/dim]")
|
||||
console.print(f"[dim] 📋 Chapters: {', '.join(chapter_list)}[/dim]")
|
||||
else:
|
||||
print(f"🚀 Building {len(chapter_list)} chapters together ({format_type})")
|
||||
print(f" 📋 Chapters: {', '.join(chapter_list)}")
|
||||
console.print(f"[green]🚀 Building {len(chapter_list)} chapters together[/green] [dim]({format_type})[/dim]")
|
||||
console.print(f"[dim] 📋 Chapters: {', '.join(chapter_list)}[/dim]")
|
||||
|
||||
return self.build_multiple_unified(chapter_list, format_type)
|
||||
|
||||
@@ -629,10 +535,7 @@ class BookBinder:
|
||||
for chapter in chapter_list:
|
||||
chapter_file = self.find_chapter_file(chapter)
|
||||
if not chapter_file:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[red]❌ Chapter not found: {chapter}[/red]")
|
||||
else:
|
||||
print(f"❌ Chapter not found: {chapter}")
|
||||
console.print(f"[red]❌ Chapter not found: {chapter}[/red]")
|
||||
return False
|
||||
chapter_files.append(chapter_file)
|
||||
|
||||
@@ -651,10 +554,7 @@ class BookBinder:
|
||||
# Ensure config is clean (remove any render restrictions)
|
||||
self.ensure_clean_config(config_file)
|
||||
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[green]🚀 Building {len(chapter_list)} chapters together[/green] [dim]({format_type})[/dim]")
|
||||
else:
|
||||
print(f"🚀 Building {len(chapter_list)} chapters together ({format_type})")
|
||||
console.print(f"[green]🚀 Building {len(chapter_list)} chapters together[/green] [dim]({format_type})[/dim]")
|
||||
|
||||
# Set up unified fast build mode for all chapters
|
||||
success = self.set_fast_build_mode_multiple(config_file, chapter_files)
|
||||
@@ -670,20 +570,14 @@ class BookBinder:
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
# Build with unified project.render configuration
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[yellow] 🔨 Building all chapters in single render...[/yellow]")
|
||||
else:
|
||||
print(" 🔨 Building all chapters in single render...")
|
||||
console.print("[yellow] 🔨 Building all chapters in single render...[/yellow]")
|
||||
|
||||
# Render project with all target chapters
|
||||
render_cmd = ["quarto", "render", "--to", format_arg]
|
||||
|
||||
# Show the raw command being executed
|
||||
cmd_str = " ".join(render_cmd)
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[blue] 💻 Command: {cmd_str}[/blue]")
|
||||
else:
|
||||
print(f" 💻 Command: {cmd_str}")
|
||||
console.print(f"[blue] 💻 Command: {cmd_str}[/blue]")
|
||||
|
||||
# Capture output to find the created file and open it
|
||||
result = self.run_command(
|
||||
@@ -694,10 +588,7 @@ class BookBinder:
|
||||
)
|
||||
|
||||
if result:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f" ✅ Unified build complete: build/{build_subdir}/")
|
||||
else:
|
||||
print(f" ✅ Unified build complete: build/{build_subdir}/")
|
||||
console.print(f" ✅ Unified build complete: build/{build_subdir}/")
|
||||
|
||||
# Open browser for HTML builds
|
||||
if format_type == "html" and result:
|
||||
@@ -705,16 +596,12 @@ class BookBinder:
|
||||
|
||||
return True
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[red]❌ Build failed[/red]")
|
||||
else:
|
||||
print("❌ Build failed")
|
||||
console.print(f"[red]❌ Build failed[/red]")
|
||||
return False
|
||||
|
||||
finally:
|
||||
# Always restore config, even if build fails
|
||||
if RICH_AVAILABLE:
|
||||
console.print(" 🛡️ Ensuring pristine config restoration...")
|
||||
console.print(" 🛡️ Ensuring pristine config restoration...")
|
||||
|
||||
self.restore_config(config_file)
|
||||
|
||||
@@ -723,26 +610,17 @@ class BookBinder:
|
||||
# Find the actual chapter file
|
||||
chapter_file = self.find_chapter_file(chapter)
|
||||
if not chapter_file:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[red]❌ No chapter found matching '{chapter}'[/red]")
|
||||
console.print("[yellow]💡 Available chapters:[/yellow]")
|
||||
self.show_chapters()
|
||||
else:
|
||||
print(f"❌ No chapter found matching '{chapter}'")
|
||||
print("💡 Available chapters:")
|
||||
self.show_chapters()
|
||||
console.print(f"[red]❌ No chapter found matching '{chapter}'[/red]")
|
||||
console.print("[yellow]💡 Available chapters:[/yellow]")
|
||||
self.show_chapters()
|
||||
return False
|
||||
|
||||
# Get relative path from book directory
|
||||
target_path = str(chapter_file.relative_to(self.book_dir))
|
||||
chapter_name = str(chapter_file.relative_to(self.book_dir / "contents")).replace(".qmd", "")
|
||||
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[green]🚀 Building[/green] [bold]{chapter_name}[/bold] [dim]({format_type})[/dim]")
|
||||
console.print(f"[dim] ✅ Found: {chapter_file}[/dim]")
|
||||
else:
|
||||
print(f"🚀 Building {chapter_name} ({format_type})")
|
||||
print(f" ✅ Found: {chapter_file}")
|
||||
console.print(f"[green]🚀 Building[/green] [bold]{chapter_name}[/bold] [dim]({format_type})[/dim]")
|
||||
console.print(f"[dim] ✅ Found: {chapter_file}[/dim]")
|
||||
|
||||
# Setup configuration
|
||||
config_file = self.html_config if format_type == "html" else self.pdf_config
|
||||
@@ -764,35 +642,23 @@ class BookBinder:
|
||||
|
||||
# Setup signal handler to restore config on Ctrl+C
|
||||
def signal_handler(signum, frame):
|
||||
if RICH_AVAILABLE:
|
||||
console.print("\n[yellow]🛡️ Ctrl+C detected - restoring config and exiting...[/yellow]")
|
||||
else:
|
||||
print("\n🛡️ Ctrl+C detected - restoring config and exiting...")
|
||||
console.print("\n[yellow]🛡️ Ctrl+C detected - restoring config and exiting...[/yellow]")
|
||||
self.restore_config(config_file)
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[green]✅ Config restored to pristine state[/green]")
|
||||
else:
|
||||
print("✅ Config restored to pristine state")
|
||||
console.print("[green]✅ Config restored to pristine state[/green]")
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
# Build with project.render configuration (fast build)
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[yellow] 🔨 Building with fast build configuration...[/yellow]")
|
||||
else:
|
||||
print(" 🔨 Building with fast build configuration...")
|
||||
console.print("[yellow] 🔨 Building with fast build configuration...[/yellow]")
|
||||
|
||||
# Render project with limited file scope
|
||||
render_cmd = ["quarto", "render", "--to", format_arg]
|
||||
|
||||
# Show the raw command being executed
|
||||
cmd_str = " ".join(render_cmd)
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[blue] 💻 Command: {cmd_str}[/blue]")
|
||||
else:
|
||||
print(f" 💻 Command: {cmd_str}")
|
||||
console.print(f"[blue] 💻 Command: {cmd_str}[/blue]")
|
||||
|
||||
# Capture output to find the created file and open it
|
||||
result = self.run_command(
|
||||
@@ -809,28 +675,19 @@ class BookBinder:
|
||||
success, output = result, ""
|
||||
|
||||
if success:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[green] ✅ Fast build complete: build/{build_subdir}/[/green]")
|
||||
else:
|
||||
print(f" ✅ Fast build complete: build/{build_subdir}/")
|
||||
console.print(f"[green] ✅ Fast build complete: build/{build_subdir}/[/green]")
|
||||
|
||||
# Automatically open the output file if HTML and requested
|
||||
if format_type == "html" and output and open_browser:
|
||||
self.open_output_file(output)
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[red] ❌ Build failed[/red]")
|
||||
else:
|
||||
print(" ❌ Build failed")
|
||||
console.print("[red] ❌ Build failed[/red]")
|
||||
|
||||
return success
|
||||
|
||||
finally:
|
||||
# Always restore config (bulletproof cleanup)
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[yellow] 🛡️ Ensuring pristine config restoration...[/yellow]")
|
||||
else:
|
||||
print(" 🛡️ Ensuring pristine config restoration...")
|
||||
console.print("[yellow] 🛡️ Ensuring pristine config restoration...[/yellow]")
|
||||
|
||||
self.restore_config(config_file)
|
||||
|
||||
@@ -839,19 +696,13 @@ class BookBinder:
|
||||
# Find the actual chapter file
|
||||
chapter_file = self.find_chapter_file(chapter)
|
||||
if not chapter_file:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[red]❌ No chapter found matching '{chapter}'[/red]")
|
||||
else:
|
||||
print(f"❌ No chapter found matching '{chapter}'")
|
||||
console.print(f"[red]❌ No chapter found matching '{chapter}'[/red]")
|
||||
return False
|
||||
|
||||
target_path = str(chapter_file.relative_to(self.book_dir))
|
||||
chapter_name = str(chapter_file.relative_to(self.book_dir / "contents")).replace(".qmd", "")
|
||||
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[blue]🌐 Starting preview for[/blue] [bold]{chapter_name}[/bold]")
|
||||
else:
|
||||
print(f"🌐 Starting preview for {chapter_name}")
|
||||
console.print(f"[blue]🌐 Starting preview for[/blue] [bold]{chapter_name}[/bold]")
|
||||
|
||||
# Setup for HTML preview
|
||||
config_file = self.html_config
|
||||
@@ -862,15 +713,9 @@ class BookBinder:
|
||||
|
||||
# Setup signal handler to restore config on exit
|
||||
def signal_handler(signum, frame):
|
||||
if RICH_AVAILABLE:
|
||||
console.print("\n[yellow]🛡️ Ctrl+C detected - restoring config and exiting...[/yellow]")
|
||||
else:
|
||||
print("\n🛡️ Ctrl+C detected - restoring config and exiting...")
|
||||
console.print("\n[yellow]🛡️ Ctrl+C detected - restoring config and exiting...[/yellow]")
|
||||
self.restore_config(config_file)
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[green]✅ Config restored to pristine state[/green]")
|
||||
else:
|
||||
print("✅ Config restored to pristine state")
|
||||
console.print("[green]✅ Config restored to pristine state[/green]")
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
@@ -882,10 +727,7 @@ class BookBinder:
|
||||
|
||||
# Show the raw command being executed
|
||||
cmd_str = " ".join(preview_cmd)
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[blue] 💻 Command: {cmd_str}[/blue]")
|
||||
else:
|
||||
print(f" 💻 Command: {cmd_str}")
|
||||
console.print(f"[blue] 💻 Command: {cmd_str}[/blue]")
|
||||
|
||||
subprocess.run(
|
||||
preview_cmd,
|
||||
@@ -900,20 +742,13 @@ class BookBinder:
|
||||
except Exception as e:
|
||||
# Always restore config on error
|
||||
self.restore_config(config_file)
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[red]❌ Preview failed: {e}[/red]")
|
||||
else:
|
||||
print(f"❌ Preview failed: {e}")
|
||||
console.print(f"[red]❌ Preview failed: {e}[/red]")
|
||||
return False
|
||||
|
||||
def clean(self):
|
||||
"""Clean build artifacts and restore configs"""
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[bold blue]🧹 Fast Build Cleanup[/bold blue]")
|
||||
console.print("[dim]💡 Restoring master configs and basic cleanup[/dim]")
|
||||
else:
|
||||
print("🧹 Fast Build Cleanup")
|
||||
print("💡 Restoring master configs and basic cleanup")
|
||||
console.print("[bold blue]🧹 Fast Build Cleanup[/bold blue]")
|
||||
console.print("[dim]💡 Restoring master configs and basic cleanup[/dim]")
|
||||
|
||||
# Restore HTML config
|
||||
html_config = self.book_dir / "_quarto-html.yml"
|
||||
@@ -927,15 +762,9 @@ class BookBinder:
|
||||
symlink_path = self.book_dir / "_quarto.yml"
|
||||
if symlink_path.exists() and symlink_path.is_symlink():
|
||||
target = symlink_path.readlink()
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[dim] 🔗 Current symlink: _quarto.yml → {target}[/dim]")
|
||||
else:
|
||||
print(f" 🔗 Current symlink: _quarto.yml → {target}")
|
||||
console.print(f"[dim] 🔗 Current symlink: _quarto.yml → {target}[/dim]")
|
||||
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[green] ✅ All configs restored to clean state[/green]")
|
||||
else:
|
||||
print(" ✅ All configs restored to clean state")
|
||||
console.print("[green] ✅ All configs restored to clean state[/green]")
|
||||
|
||||
# Clean build artifacts
|
||||
artifacts_to_clean = [
|
||||
@@ -952,29 +781,17 @@ class BookBinder:
|
||||
else:
|
||||
artifact_path.unlink()
|
||||
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[yellow] 🗑️ Removing: {artifact_path.name} ({description})[/yellow]")
|
||||
else:
|
||||
print(f" 🗑️ Removing: {artifact_path.name} ({description})")
|
||||
console.print(f"[yellow] 🗑️ Removing: {artifact_path.name} ({description})[/yellow]")
|
||||
cleaned_count += 1
|
||||
|
||||
if cleaned_count > 0:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[green] ✅ Cleaned {cleaned_count} items successfully[/green]")
|
||||
else:
|
||||
print(f" ✅ Cleaned {cleaned_count} items successfully")
|
||||
console.print(f"[green] ✅ Cleaned {cleaned_count} items successfully[/green]")
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[green] ✅ No artifacts to clean[/green]")
|
||||
else:
|
||||
print(" ✅ No artifacts to clean")
|
||||
console.print("[green] ✅ No artifacts to clean[/green]")
|
||||
|
||||
def check_artifacts(self):
|
||||
"""Check for build artifacts that shouldn't be committed"""
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[blue]🔍 Checking for build artifacts...[/blue]")
|
||||
else:
|
||||
print("🔍 Checking for build artifacts...")
|
||||
console.print("[blue]🔍 Checking for build artifacts...[/blue]")
|
||||
|
||||
# Check for artifacts that shouldn't be committed
|
||||
artifacts_found = []
|
||||
@@ -992,37 +809,22 @@ class BookBinder:
|
||||
artifacts_found.append((path, description))
|
||||
|
||||
if artifacts_found:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[yellow]⚠️ Build artifacts detected:[/yellow]")
|
||||
for path, description in artifacts_found:
|
||||
console.print(f"[yellow] 📄 {path} ({description})[/yellow]")
|
||||
console.print("[blue]💡 Run './binder clean' to remove these artifacts[/blue]")
|
||||
else:
|
||||
print("⚠️ Build artifacts detected:")
|
||||
for path, description in artifacts_found:
|
||||
print(f" 📄 {path} ({description})")
|
||||
print("💡 Run './binder clean' to remove these artifacts")
|
||||
console.print("[yellow]⚠️ Build artifacts detected:[/yellow]")
|
||||
for path, description in artifacts_found:
|
||||
console.print(f"[yellow] 📄 {path} ({description})[/yellow]")
|
||||
console.print("[blue]💡 Run './binder clean' to remove these artifacts[/blue]")
|
||||
return False
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[green]✅ No build artifacts detected.[/green]")
|
||||
else:
|
||||
print("✅ No build artifacts detected.")
|
||||
console.print("[green]✅ No build artifacts detected.[/green]")
|
||||
return True
|
||||
|
||||
def switch(self, format_type):
|
||||
"""Switch configuration format"""
|
||||
if format_type not in ["html", "pdf"]:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[red]❌ Format must be 'html' or 'pdf'[/red]")
|
||||
else:
|
||||
print("❌ Format must be 'html' or 'pdf'")
|
||||
console.print("[red]❌ Format must be 'html' or 'pdf'[/red]")
|
||||
return False
|
||||
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[blue]🔗 Switching to {format_type} config...[/blue]")
|
||||
else:
|
||||
print(f"🔗 Switching to {format_type} config...")
|
||||
console.print(f"[blue]🔗 Switching to {format_type} config...[/blue]")
|
||||
|
||||
# Clean up first
|
||||
self.clean()
|
||||
@@ -1030,19 +832,13 @@ class BookBinder:
|
||||
# Setup new symlink
|
||||
config_name = self.setup_symlink(format_type)
|
||||
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[green] ✅ _quarto.yml → {config_name}[/green]")
|
||||
else:
|
||||
print(f" ✅ _quarto.yml → {config_name}")
|
||||
console.print(f"[green] ✅ _quarto.yml → {config_name}[/green]")
|
||||
|
||||
return True
|
||||
|
||||
def build_full(self, format_type="html"):
|
||||
"""Build full book in specified format"""
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[green]🔨 Building full {format_type.upper()} book...[/green]")
|
||||
else:
|
||||
print(f"🔨 Building full {format_type.upper()} book...")
|
||||
console.print(f"[green]🔨 Building full {format_type.upper()} book...[/green]")
|
||||
|
||||
# Create build directory
|
||||
build_subdir = format_type
|
||||
@@ -1052,17 +848,11 @@ class BookBinder:
|
||||
config_name = self.setup_symlink(format_type)
|
||||
render_cmd = ["quarto", "render", "--to", "html" if format_type == "html" else "titlepage-pdf"]
|
||||
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[blue] 🔗 Using {config_name}[/blue]")
|
||||
else:
|
||||
print(f" 🔗 Using {config_name}")
|
||||
console.print(f"[blue] 🔗 Using {config_name}[/blue]")
|
||||
|
||||
# Show the raw command being executed
|
||||
cmd_str = " ".join(render_cmd)
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[blue] 💻 Command: {cmd_str}[/blue]")
|
||||
else:
|
||||
print(f" 💻 Command: {cmd_str}")
|
||||
console.print(f"[blue] 💻 Command: {cmd_str}[/blue]")
|
||||
|
||||
success = self.run_command(
|
||||
render_cmd,
|
||||
@@ -1071,90 +861,38 @@ class BookBinder:
|
||||
)
|
||||
|
||||
if success:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[green] ✅ {format_type.upper()} build complete: build/{build_subdir}/[/green]")
|
||||
else:
|
||||
print(f" ✅ {format_type.upper()} build complete: build/{build_subdir}/")
|
||||
console.print(f"[green] ✅ {format_type.upper()} build complete: build/{build_subdir}/[/green]")
|
||||
|
||||
return success
|
||||
|
||||
def preview_full(self, format_type="html"):
|
||||
"""Start full preview server"""
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[blue]🌐 Starting full {format_type.upper()} preview server...[/blue]")
|
||||
else:
|
||||
print(f"🌐 Starting full {format_type.upper()} preview server...")
|
||||
console.print(f"[blue]🌐 Starting full {format_type.upper()} preview server...[/blue]")
|
||||
|
||||
# Setup config
|
||||
config_name = self.setup_symlink(format_type)
|
||||
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[blue] 🔗 Using {config_name}[/blue]")
|
||||
console.print("[dim] 🛑 Press Ctrl+C to stop the server[/dim]")
|
||||
else:
|
||||
print(f" 🔗 Using {config_name}")
|
||||
print(" 🛑 Press Ctrl+C to stop the server")
|
||||
console.print(f"[blue] 🔗 Using {config_name}[/blue]")
|
||||
console.print("[dim] 🛑 Press Ctrl+C to stop the server[/dim]")
|
||||
|
||||
try:
|
||||
preview_cmd = ["quarto", "preview"]
|
||||
|
||||
# Show the raw command being executed
|
||||
cmd_str = " ".join(preview_cmd)
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[blue] 💻 Command: {cmd_str}[/blue]")
|
||||
else:
|
||||
print(f" 💻 Command: {cmd_str}")
|
||||
console.print(f"[blue] 💻 Command: {cmd_str}[/blue]")
|
||||
|
||||
subprocess.run(preview_cmd, cwd=self.book_dir)
|
||||
return True
|
||||
except KeyboardInterrupt:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("\n[yellow]🛑 Preview server stopped[/yellow]")
|
||||
else:
|
||||
print("\n🛑 Preview server stopped")
|
||||
console.print("\n[yellow]🛑 Preview server stopped[/yellow]")
|
||||
return True
|
||||
except Exception as e:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[red]❌ Preview failed: {e}[/red]")
|
||||
else:
|
||||
print(f"❌ Preview failed: {e}")
|
||||
console.print(f"[red]❌ Preview failed: {e}[/red]")
|
||||
return False
|
||||
|
||||
def show_help(self):
|
||||
"""Display beautiful help screen"""
|
||||
if not RICH_AVAILABLE:
|
||||
print("""
|
||||
📚 Book Binder - Self-Contained MLSysBook CLI
|
||||
|
||||
Fast Chapter Commands:
|
||||
build <chapter> [format] Build single chapter (html/pdf)
|
||||
preview <chapter> Build and preview single chapter
|
||||
|
||||
Full Book Commands:
|
||||
build-full [format] Build complete book (html/pdf)
|
||||
preview-full [format] Preview complete book (html/pdf)
|
||||
|
||||
Management:
|
||||
clean [deep] [dry] Clean up configurations and build artifacts
|
||||
check Check for build artifacts
|
||||
switch <format> Switch config (html/pdf)
|
||||
status Show current status
|
||||
list List available chapters
|
||||
help Show this help
|
||||
|
||||
Examples:
|
||||
./binder build intro # Build introduction chapter (HTML)
|
||||
./binder b dl_primer pdf # Build deep learning primer (PDF)
|
||||
./binder preview ops # Preview ops chapter
|
||||
./binder build-full pdf # Build complete PDF book
|
||||
./binder switch pdf # Switch to PDF config
|
||||
./binder clean # Clean artifacts and configs
|
||||
./binder check # Check for build artifacts
|
||||
./binder status # Show current status
|
||||
""")
|
||||
return
|
||||
|
||||
# Create beautiful help panels
|
||||
fast_table = Table(show_header=True, header_style="bold green", box=None)
|
||||
fast_table.add_column("Command", style="green", width=22)
|
||||
fast_table.add_column("Description", style="white")
|
||||
@@ -1210,7 +948,7 @@ Examples:
|
||||
console.print(Panel(shortcuts_table, title="🚀 Shortcuts", border_style="cyan"))
|
||||
|
||||
# Pro Tips
|
||||
console.print(Padding(Panel("", title="💡 Pro Tips", border_style="blue"), (1, 0)))
|
||||
console.print(Panel("", title="💡 Pro Tips", border_style="blue"))
|
||||
|
||||
examples = Text()
|
||||
examples.append("🎯 Power User Examples:\n", style="bold magenta")
|
||||
@@ -1259,10 +997,7 @@ def main():
|
||||
try:
|
||||
if command == "build":
|
||||
if len(sys.argv) < 3:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[red]❌ Usage: ./binder build <chapter[,chapter2,...]> [format][/red]")
|
||||
else:
|
||||
print("❌ Usage: ./binder build <chapter[,chapter2,...]> [format]")
|
||||
console.print("[red]❌ Usage: ./binder build <chapter[,chapter2,...]> [format][/red]")
|
||||
return
|
||||
chapter = sys.argv[2]
|
||||
format_type = sys.argv[3] if len(sys.argv) > 3 else "html"
|
||||
@@ -1270,10 +1005,7 @@ def main():
|
||||
|
||||
elif command == "preview":
|
||||
if len(sys.argv) < 3:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[red]❌ Usage: ./binder preview <chapter>[/red]")
|
||||
else:
|
||||
print("❌ Usage: ./binder preview <chapter>")
|
||||
console.print("[red]❌ Usage: ./binder preview <chapter>[/red]")
|
||||
return
|
||||
chapter = sys.argv[2]
|
||||
binder.preview(chapter)
|
||||
@@ -1281,20 +1013,14 @@ def main():
|
||||
elif command == "build-full":
|
||||
format_type = sys.argv[2] if len(sys.argv) > 2 else "html"
|
||||
if format_type not in ["html", "pdf"]:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[red]❌ Format must be 'html' or 'pdf'[/red]")
|
||||
else:
|
||||
print("❌ Format must be 'html' or 'pdf'")
|
||||
console.print("[red]❌ Format must be 'html' or 'pdf'[/red]")
|
||||
return
|
||||
binder.build_full(format_type)
|
||||
|
||||
elif command == "preview-full":
|
||||
format_type = sys.argv[2] if len(sys.argv) > 2 else "html"
|
||||
if format_type not in ["html", "pdf"]:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[red]❌ Format must be 'html' or 'pdf'[/red]")
|
||||
else:
|
||||
print("❌ Format must be 'html' or 'pdf'")
|
||||
console.print("[red]❌ Format must be 'html' or 'pdf'[/red]")
|
||||
return
|
||||
binder.preview_full(format_type)
|
||||
|
||||
@@ -1308,10 +1034,7 @@ def main():
|
||||
|
||||
elif command == "switch":
|
||||
if len(sys.argv) < 3:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("[red]❌ Usage: ./binder switch <html|pdf>[/red]")
|
||||
else:
|
||||
print("❌ Usage: ./binder switch <html|pdf>")
|
||||
console.print("[red]❌ Usage: ./binder switch <html|pdf>[/red]")
|
||||
return
|
||||
format_type = sys.argv[2]
|
||||
binder.switch(format_type)
|
||||
@@ -1326,23 +1049,13 @@ def main():
|
||||
binder.show_help()
|
||||
|
||||
else:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[red]❌ Unknown command: {command}[/red]")
|
||||
console.print("[yellow]💡 Use './binder help' to see available commands[/yellow]")
|
||||
else:
|
||||
print(f"❌ Unknown command: {command}")
|
||||
print("💡 Use './binder help' to see available commands")
|
||||
console.print(f"[red]❌ Unknown command: {command}[/red]")
|
||||
console.print("[yellow]💡 Use './binder help' to see available commands[/yellow]")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
if RICH_AVAILABLE:
|
||||
console.print("\n[yellow]👋 Goodbye![/yellow]")
|
||||
else:
|
||||
print("\n👋 Goodbye!")
|
||||
console.print("\n[yellow]👋 Goodbye![/yellow]")
|
||||
except Exception as e:
|
||||
if RICH_AVAILABLE:
|
||||
console.print(f"[red]❌ Error: {e}[/red]")
|
||||
else:
|
||||
print(f"❌ Error: {e}")
|
||||
console.print(f"[red]❌ Error: {e}[/red]")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user