mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-03-11 17:49:25 -05:00
Adds newsletter infrastructure: CLI commands (new, list, preview, publish, fetch, status) integrated into binder, Quarto archive site config for mlsysbook.ai/newsletter/, and 12-month editorial content plan. Drafts are gitignored for private local writing; sent newsletters are committed as the public archive. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
558 lines
25 KiB
Python
558 lines
25 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
MLSysBook CLI - Modular Entry Point
|
|
|
|
A refactored, modular command-line interface for building, previewing,
|
|
and managing the Machine Learning Systems textbook.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from rich.console import Console
|
|
from rich.table import Table
|
|
from rich.panel import Panel
|
|
from rich.text import Text
|
|
|
|
# Import our modular components
|
|
# Ensure the book directory is in sys.path so 'from cli...' works regardless of CWD
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from cli.core.config import ConfigManager
|
|
from cli.core.discovery import ChapterDiscovery, AmbiguousChapterError
|
|
from cli.commands.build import BuildCommand
|
|
from cli.commands.preview import PreviewCommand
|
|
from cli.commands.doctor import DoctorCommand
|
|
from cli.commands.clean import CleanCommand
|
|
from cli.commands.maintenance import MaintenanceCommand
|
|
from cli.commands.debug import DebugCommand
|
|
from cli.commands.validate import ValidateCommand
|
|
from cli.commands.formatting import FormatCommand
|
|
from cli.commands.info import InfoCommand
|
|
from cli.commands.bib import BibCommand
|
|
from cli.commands.render import RenderCommand
|
|
from cli.commands.newsletter import NewsletterCommand
|
|
|
|
console = Console()
|
|
|
|
|
|
class MLSysBookCLI:
|
|
"""Main CLI application class."""
|
|
|
|
def __init__(self, verbose: bool = False, open_after: bool = False):
|
|
"""Initialize the CLI with all components.
|
|
|
|
Args:
|
|
verbose: If True, stream build output in real-time
|
|
open_after: If True, open build output after successful build
|
|
"""
|
|
self.root_dir = Path.cwd()
|
|
self.verbose = verbose
|
|
self.open_after = open_after
|
|
|
|
# Initialize core components
|
|
self.config_manager = ConfigManager(self.root_dir)
|
|
self.chapter_discovery = ChapterDiscovery(self.config_manager.book_dir)
|
|
|
|
# Initialize command handlers
|
|
self.build_command = BuildCommand(self.config_manager, self.chapter_discovery, verbose=verbose, open_after=open_after)
|
|
self.preview_command = PreviewCommand(self.config_manager, self.chapter_discovery)
|
|
self.doctor_command = DoctorCommand(self.config_manager, self.chapter_discovery)
|
|
self.clean_command = CleanCommand(self.config_manager, self.chapter_discovery)
|
|
self.maintenance_command = MaintenanceCommand(self.config_manager, self.chapter_discovery)
|
|
self.debug_command = DebugCommand(self.config_manager, self.chapter_discovery, verbose=verbose)
|
|
self.validate_command = ValidateCommand(self.config_manager, self.chapter_discovery)
|
|
self.format_command = FormatCommand(self.config_manager, self.chapter_discovery)
|
|
self.info_command = InfoCommand(self.config_manager, self.chapter_discovery)
|
|
self.bib_command = BibCommand(self.config_manager, self.chapter_discovery)
|
|
self.render_command = RenderCommand(self.config_manager, self.chapter_discovery)
|
|
self.newsletter_command = NewsletterCommand(self.config_manager, verbose=verbose)
|
|
|
|
def show_banner(self):
|
|
"""Display the CLI banner."""
|
|
banner = Panel(
|
|
"[bold blue]📚 MLSysBook CLI v2.0[/bold blue]\n"
|
|
"[dim]⚡ Modular, maintainable, and fast[/dim]",
|
|
border_style="cyan"
|
|
)
|
|
console.print(banner)
|
|
|
|
def show_help(self):
|
|
"""Display help information."""
|
|
self.show_banner()
|
|
|
|
# Fast Chapter Commands
|
|
fast_table = Table(show_header=True, header_style="bold green", box=None)
|
|
fast_table.add_column("Command", style="green", width=35)
|
|
fast_table.add_column("Description", style="white", width=30)
|
|
fast_table.add_column("Example", style="dim", width=30)
|
|
|
|
fast_table.add_row("build [fmt] [chapter[,ch2,...]]", "Build HTML/PDF/EPUB by format", "./binder build pdf intro")
|
|
fast_table.add_row("preview [chapter[,ch2,...]]", "Start live dev server with hot reload", "./binder preview intro")
|
|
fast_table.add_row("pdf|epub|html reset [--vol1|--vol2]", "Reset commented files in config", "./binder pdf reset --vol1")
|
|
|
|
# Volume Commands
|
|
vol_table = Table(show_header=True, header_style="bold magenta", box=None)
|
|
vol_table.add_column("Command", style="magenta", width=35)
|
|
vol_table.add_column("Description", style="white", width=30)
|
|
vol_table.add_column("Example", style="dim", width=30)
|
|
|
|
vol_table.add_row("build html --vol1", "Build Volume I website", "./binder build html --vol1")
|
|
vol_table.add_row("build html --vol2", "Build Volume II website", "./binder build html --vol2")
|
|
vol_table.add_row("build pdf --vol1", "Build Volume I as PDF", "./binder build pdf --vol1")
|
|
vol_table.add_row("build pdf --vol2", "Build Volume II as PDF", "./binder build pdf --vol2")
|
|
vol_table.add_row("build epub --vol1", "Build Volume I as EPUB", "./binder build epub --vol1")
|
|
vol_table.add_row("build epub --vol2", "Build Volume II as EPUB", "./binder build epub --vol2")
|
|
vol_table.add_row("list --vol1", "List Volume I chapters", "./binder list --vol1")
|
|
vol_table.add_row("list --vol2", "List Volume II chapters", "./binder list --vol2")
|
|
|
|
# Full Book Commands
|
|
full_table = Table(show_header=True, header_style="bold blue", box=None)
|
|
full_table.add_column("Command", style="blue", width=35)
|
|
full_table.add_column("Description", style="white", width=30)
|
|
full_table.add_column("Example", style="dim", width=30)
|
|
|
|
full_table.add_row("build", "Build entire book as static HTML", "./binder build")
|
|
full_table.add_row("build html --all", "Build ALL chapters using HTML config", "./binder build html --all")
|
|
full_table.add_row("preview", "Start live dev server for entire book", "./binder preview")
|
|
full_table.add_row("build pdf --all", "Build full book (both volumes)", "./binder build pdf --all")
|
|
full_table.add_row("build epub --all", "Build full book (both volumes)", "./binder build epub --all")
|
|
|
|
# Quality Commands
|
|
quality_table = Table(show_header=True, header_style="bold yellow", box=None)
|
|
quality_table.add_column("Command", style="yellow", width=38)
|
|
quality_table.add_column("Description", style="white", width=30)
|
|
quality_table.add_column("Example", style="dim", width=28)
|
|
|
|
quality_table.add_row("check <group> [--scope ...]", "Run validation checks", "./binder check refs")
|
|
quality_table.add_row("check all", "Run all validation checks", "./binder check all --vol1")
|
|
quality_table.add_row("check spelling", "Spell check prose and TikZ", "./binder check spelling")
|
|
quality_table.add_row("check sources", "Validate source citations", "./binder check sources")
|
|
quality_table.add_row("fix <topic> <action>", "Fix/manage content", "./binder fix headers add")
|
|
quality_table.add_row("format <target>", "Auto-format content", "./binder format tables")
|
|
quality_table.add_row("info stats [--by-chapter]", "Book statistics (words, figs, ...)", "./binder info stats --vol1")
|
|
quality_table.add_row("info figures [--format csv]", "Extract figure list", "./binder info figures --vol1")
|
|
quality_table.add_row("info concepts|headers|acronyms", "Extract concepts, headers, acronyms", "./binder info concepts --vol1")
|
|
quality_table.add_row("bib list|clean|update|sync", "Bibliography management", "./binder bib sync --vol1")
|
|
quality_table.add_row("render plots [--vol1|chapter]", "Render matplotlib plots to PNG gallery", "./binder render plots --vol1")
|
|
|
|
# Newsletter Commands
|
|
nl_table = Table(show_header=True, header_style="bold magenta", box=None)
|
|
nl_table.add_column("Command", style="magenta", width=38)
|
|
nl_table.add_column("Description", style="white", width=30)
|
|
nl_table.add_column("Example", style="dim", width=28)
|
|
|
|
nl_table.add_row("newsletter new <title>", "Create a new draft", './binder newsletter new "Vol2 Update"')
|
|
nl_table.add_row("newsletter list", "List drafts and sent", "./binder newsletter list")
|
|
nl_table.add_row("newsletter preview <slug>", "Preview a draft", "./binder newsletter preview vol2")
|
|
nl_table.add_row("newsletter publish <slug>", "Push draft to Buttondown", "./binder newsletter publish vol2")
|
|
nl_table.add_row("newsletter fetch", "Pull sent emails for website", "./binder newsletter fetch")
|
|
nl_table.add_row("newsletter status", "Subscriber count & recent", "./binder newsletter status")
|
|
|
|
# Management Commands
|
|
mgmt_table = Table(show_header=True, header_style="bold blue", box=None)
|
|
mgmt_table.add_column("Command", style="green", width=38)
|
|
mgmt_table.add_column("Description", style="white", width=30)
|
|
mgmt_table.add_column("Example", style="dim", width=28)
|
|
|
|
mgmt_table.add_row("debug <fmt> --vol1|--vol2", "Find failing chapter + section", "./binder debug pdf --vol1")
|
|
mgmt_table.add_row("clean", "Clean build artifacts", "./binder clean")
|
|
mgmt_table.add_row("switch <format>", "Switch active config", "./binder switch pdf")
|
|
mgmt_table.add_row("list", "List available chapters", "./binder list")
|
|
mgmt_table.add_row("status", "Show current config status", "./binder status")
|
|
mgmt_table.add_row("doctor", "Run comprehensive health check", "./binder doctor")
|
|
mgmt_table.add_row("setup", "Setup development environment", "./binder setup")
|
|
mgmt_table.add_row("help", "Show this help", "./binder help")
|
|
|
|
# Display tables
|
|
console.print(Panel(fast_table, title="⚡ Fast Chapter Commands", border_style="green"))
|
|
console.print(Panel(vol_table, title="📖 Volume Commands", border_style="magenta"))
|
|
console.print(Panel(full_table, title="📚 Full Book Commands", border_style="blue"))
|
|
console.print(Panel(quality_table, title="✅ Quality & Formatting", border_style="yellow"))
|
|
console.print(Panel(nl_table, title="📬 Newsletter", border_style="magenta"))
|
|
console.print(Panel(mgmt_table, title="🔧 Management", border_style="cyan"))
|
|
|
|
# Pro Tips
|
|
examples = Text()
|
|
examples.append("🎯 Modular CLI Examples:\n", style="bold magenta")
|
|
examples.append(" ./binder build pdf --vol1 ", style="cyan")
|
|
examples.append("# Build Volume I as PDF\n", style="dim")
|
|
examples.append(" ./binder build pdf --vol2 ", style="cyan")
|
|
examples.append("# Build Volume II as PDF\n", style="dim")
|
|
examples.append(" ./binder build pdf vol1/intro ", style="cyan")
|
|
examples.append("# Build specific chapter (disambiguate with vol prefix)\n", style="dim")
|
|
examples.append(" ./binder build pdf --all ", style="cyan")
|
|
examples.append("# Build entire book as PDF (both volumes)\n", style="dim")
|
|
examples.append(" ./binder list --vol1 ", style="cyan")
|
|
examples.append("# List only Volume I chapters\n", style="dim")
|
|
|
|
console.print(Panel(examples, title="💡 Pro Tips", border_style="magenta"))
|
|
|
|
# Global Options
|
|
options_text = Text()
|
|
options_text.append("🔧 Global Options:\n", style="bold yellow")
|
|
options_text.append(" -v, --verbose ", style="yellow")
|
|
options_text.append("Stream build output in real-time\n", style="dim")
|
|
options_text.append(" -o, --open ", style="yellow")
|
|
options_text.append("Open output after successful build\n", style="dim")
|
|
options_text.append(" Example: ", style="dim")
|
|
options_text.append("./binder build pdf --vol1 -v -o", style="cyan")
|
|
|
|
console.print(Panel(options_text, title="⚙️ Options", border_style="yellow"))
|
|
|
|
def _parse_build_args(self, args):
|
|
"""Parse `binder build` arguments into format, scope, and targets."""
|
|
format_type = None
|
|
volume = None
|
|
build_all = False
|
|
remaining = []
|
|
|
|
for arg in args:
|
|
lower = arg.lower()
|
|
if lower == "--vol1":
|
|
volume = "vol1"
|
|
elif lower == "--vol2":
|
|
volume = "vol2"
|
|
elif lower == "--all":
|
|
build_all = True
|
|
elif format_type is None and lower in ("html", "pdf", "epub"):
|
|
format_type = lower
|
|
else:
|
|
remaining.append(arg)
|
|
|
|
if format_type is None:
|
|
format_type = "html"
|
|
|
|
chapters_arg = remaining[0] if remaining else None
|
|
return format_type, volume, build_all, chapters_arg
|
|
|
|
def handle_build_command(self, args):
|
|
"""Handle unified build command.
|
|
|
|
Usage:
|
|
./binder build
|
|
./binder build pdf
|
|
./binder build epub --vol2
|
|
./binder build html intro,frameworks
|
|
"""
|
|
if "-h" in args or "--help" in args:
|
|
console.print("Usage: ./binder build [html|pdf|epub] [chapters] [--vol1|--vol2|--all]", markup=False)
|
|
console.print("[dim]Examples:[/dim]")
|
|
console.print("[dim] ./binder build[/dim]")
|
|
console.print("[dim] ./binder build pdf[/dim]")
|
|
console.print("[dim] ./binder build pdf intro,training --vol1[/dim]")
|
|
console.print("[dim] ./binder build html --all[/dim]")
|
|
return True
|
|
|
|
self.config_manager.show_symlink_status()
|
|
format_type, volume, build_all, chapters_arg = self._parse_build_args(args)
|
|
|
|
if build_all and chapters_arg:
|
|
console.print("[red]❌ Cannot combine explicit chapters with --all[/red]")
|
|
return False
|
|
|
|
if build_all:
|
|
if format_type == "html":
|
|
console.print("[green]🌐 Building HTML with ALL chapters...[/green]")
|
|
return self.build_command.build_html_only()
|
|
console.print(f"[green]🏗️ Building entire book ({format_type.upper()})...[/green]")
|
|
return self.build_command.build_full(format_type)
|
|
|
|
if volume and not chapters_arg:
|
|
volume_name = "Volume I" if volume == "vol1" else "Volume II"
|
|
console.print(f"[magenta]🏗️ Building {volume_name} ({format_type.upper()})...[/magenta]")
|
|
return self.build_command.build_volume(volume, format_type)
|
|
|
|
if volume and chapters_arg:
|
|
chapter_list = [ch.strip() for ch in chapters_arg.split(",")]
|
|
console.print(f"[green]🏗️ Building {format_type.upper()} chapters in {volume}: {chapters_arg}[/green]")
|
|
return self.build_command.build_chapters_with_volume(chapter_list, format_type, volume)
|
|
|
|
if chapters_arg:
|
|
chapter_list = [ch.strip() for ch in chapters_arg.split(",")]
|
|
console.print(f"[green]🏗️ Building {format_type.upper()} chapter(s): {chapters_arg}[/green]")
|
|
if format_type == "html":
|
|
return self.build_command.build_html_only(chapter_list)
|
|
return self.build_command.build_chapters(chapter_list, format_type)
|
|
|
|
console.print(f"[green]🏗️ Building entire book ({format_type.upper()})...[/green]")
|
|
if format_type == "html":
|
|
return self.build_command.build_full("html")
|
|
return self.build_command.build_full(format_type)
|
|
|
|
def handle_format_reset_command(self, format_type: str, args) -> bool:
|
|
"""Handle shorthand reset command, e.g. ./binder pdf reset --vol1."""
|
|
if not args:
|
|
console.print(f"[red]❌ Usage: ./binder {format_type} reset [--vol1|--vol2][/red]")
|
|
return False
|
|
|
|
action = args[0].lower()
|
|
if action != "reset":
|
|
console.print(f"[red]❌ Unknown {format_type} subcommand: {action}[/red]")
|
|
console.print(f"[yellow]💡 Supported: ./binder {format_type} reset [--vol1|--vol2][/yellow]")
|
|
return False
|
|
|
|
volume = None
|
|
if "--vol1" in args[1:]:
|
|
volume = "vol1"
|
|
elif "--vol2" in args[1:]:
|
|
volume = "vol2"
|
|
|
|
return self.build_command.reset_build_config(format_type, volume)
|
|
|
|
def handle_preview_command(self, args):
|
|
"""Handle preview command."""
|
|
self.config_manager.show_symlink_status()
|
|
|
|
if len(args) < 1:
|
|
# No target specified - preview entire book
|
|
console.print("[blue]🌐 Starting preview for entire book...[/blue]")
|
|
return self.preview_command.preview_full("html")
|
|
else:
|
|
# Chapter specified
|
|
chapter = args[0]
|
|
if ',' in chapter:
|
|
console.print("[yellow]⚠️ Preview only supports single chapters, not multiple[/yellow]")
|
|
console.print("[dim]💡 Use the first chapter from your list[/dim]")
|
|
chapter = chapter.split(',')[0].strip()
|
|
|
|
console.print(f"[blue]🌐 Starting preview for chapter: {chapter}[/blue]")
|
|
return self.preview_command.preview_chapter(chapter)
|
|
|
|
def handle_doctor_command(self, args):
|
|
"""Handle doctor/health check command."""
|
|
return self.doctor_command.run_health_check()
|
|
|
|
def handle_clean_command(self, args):
|
|
"""Handle clean command."""
|
|
if len(args) > 0:
|
|
target = args[0].lower()
|
|
if target in ["html", "pdf", "epub"]:
|
|
return self.clean_command.clean_format(target)
|
|
elif target == "artifacts":
|
|
return self.clean_command.clean_artifacts()
|
|
else:
|
|
console.print(f"[red]❌ Unknown clean target: {target}[/red]")
|
|
console.print("[yellow]💡 Available: html, pdf, epub, artifacts[/yellow]")
|
|
return False
|
|
else:
|
|
# Clean all
|
|
return self.clean_command.clean_all()
|
|
|
|
def handle_switch_command(self, args):
|
|
"""Handle switch command."""
|
|
if len(args) < 1:
|
|
console.print("[red]❌ Usage: ./binder switch <format>[/red]")
|
|
console.print("[yellow]💡 Available formats: html, pdf, epub[/yellow]")
|
|
return False
|
|
|
|
format_type = args[0].lower()
|
|
return self.maintenance_command.switch_format(format_type)
|
|
|
|
def handle_setup_command(self, args):
|
|
"""Handle setup command."""
|
|
return self.maintenance_command.setup_environment()
|
|
|
|
def handle_hello_command(self, args):
|
|
"""Handle hello command."""
|
|
return self.maintenance_command.show_hello()
|
|
|
|
def handle_about_command(self, args):
|
|
"""Handle about command."""
|
|
return self.maintenance_command.show_about()
|
|
|
|
def handle_check_command(self, args):
|
|
"""Handle check (validation) command group."""
|
|
return self.validate_command.run(args)
|
|
|
|
def handle_fix_command(self, args):
|
|
"""Handle fix (maintenance) namespace command."""
|
|
return self.maintenance_command.run_namespace(args)
|
|
|
|
def handle_format_command(self, args):
|
|
"""Handle format command group."""
|
|
return self.format_command.run(args)
|
|
|
|
def handle_info_command(self, args):
|
|
"""Handle info command group (stats, figures)."""
|
|
return self.info_command.run(args)
|
|
|
|
def handle_bib_command(self, args):
|
|
"""Handle bib command group (list, clean, update, sync)."""
|
|
return self.bib_command.run(args)
|
|
|
|
def handle_render_command(self, args):
|
|
"""Handle render command group (plots)."""
|
|
return self.render_command.run(args)
|
|
|
|
def handle_newsletter_command(self, args):
|
|
"""Handle newsletter command group (new, list, preview, publish, fetch, status)."""
|
|
return self.newsletter_command.run(args)
|
|
|
|
|
|
def handle_debug_command(self, args):
|
|
"""Handle debug command.
|
|
|
|
Usage:
|
|
./binder debug pdf --vol1
|
|
./binder debug html --vol2 --chapter training
|
|
"""
|
|
# Parse args: first positional is format, then flags
|
|
format_type = None
|
|
volume = None
|
|
chapter = None
|
|
|
|
i = 0
|
|
while i < len(args):
|
|
arg = args[i]
|
|
if arg == "--vol1":
|
|
volume = "vol1"
|
|
elif arg == "--vol2":
|
|
volume = "vol2"
|
|
elif arg == "--chapter" and i + 1 < len(args):
|
|
i += 1
|
|
chapter = args[i]
|
|
elif arg in ("pdf", "html", "epub") and format_type is None:
|
|
format_type = arg
|
|
else:
|
|
# Try as format or chapter
|
|
if format_type is None and arg in ("pdf", "html", "epub"):
|
|
format_type = arg
|
|
else:
|
|
console.print(f"[red]Unknown argument: {arg}[/red]")
|
|
return False
|
|
i += 1
|
|
|
|
if not format_type:
|
|
format_type = "pdf" # Default to PDF
|
|
if not volume:
|
|
console.print("[red]Please specify --vol1 or --vol2[/red]")
|
|
console.print("[yellow]Usage: ./binder debug <pdf|html|epub> --vol1|--vol2 [--chapter <name>][/yellow]")
|
|
return False
|
|
|
|
return self.debug_command.debug_build(format_type, volume, chapter)
|
|
|
|
def handle_list_command(self, args):
|
|
"""Handle list chapters command."""
|
|
volume = None
|
|
if len(args) > 0:
|
|
if args[0] == "--vol1":
|
|
volume = "vol1"
|
|
elif args[0] == "--vol2":
|
|
volume = "vol2"
|
|
|
|
self.chapter_discovery.show_chapters(volume=volume)
|
|
return True
|
|
|
|
def handle_status_command(self, args):
|
|
"""Handle status command."""
|
|
console.print("[bold blue]📊 MLSysBook CLI Status[/bold blue]")
|
|
console.print(f"[dim]Root directory: {self.root_dir}[/dim]")
|
|
console.print(f"[dim]Book directory: {self.config_manager.book_dir}[/dim]")
|
|
|
|
# Show config status
|
|
self.config_manager.show_symlink_status()
|
|
|
|
# Show chapter count
|
|
chapters = self.chapter_discovery.get_all_chapters()
|
|
console.print(f"[dim]Available chapters: {len(chapters)}[/dim]")
|
|
|
|
return True
|
|
|
|
def run(self, args):
|
|
"""Run the CLI with given arguments."""
|
|
if len(args) < 1:
|
|
self.show_help()
|
|
return True
|
|
|
|
command = args[0].lower()
|
|
command_args = args[1:]
|
|
|
|
# Command mapping
|
|
commands = {
|
|
"build": self.handle_build_command,
|
|
"preview": self.handle_preview_command,
|
|
"clean": self.handle_clean_command,
|
|
"debug": self.handle_debug_command,
|
|
"switch": self.handle_switch_command,
|
|
"list": self.handle_list_command,
|
|
"status": self.handle_status_command,
|
|
"doctor": self.handle_doctor_command,
|
|
"check": self.handle_check_command,
|
|
"fix": self.handle_fix_command,
|
|
"format": self.handle_format_command,
|
|
"info": self.handle_info_command,
|
|
"bib": self.handle_bib_command,
|
|
"render": self.handle_render_command,
|
|
"newsletter": self.handle_newsletter_command,
|
|
"setup": self.handle_setup_command,
|
|
"hello": self.handle_hello_command,
|
|
"about": self.handle_about_command,
|
|
# Aliases (backward compat)
|
|
"validate": self.handle_check_command,
|
|
"maintain": self.handle_fix_command,
|
|
|
|
"help": lambda args: self.show_help() or True,
|
|
}
|
|
|
|
# Deprecated short aliases (hard-cut cleanup).
|
|
deprecated_aliases = {
|
|
"b": "build",
|
|
"p": "preview",
|
|
"l": "list",
|
|
"s": "status",
|
|
"d": "doctor",
|
|
"h": "help",
|
|
}
|
|
if command in deprecated_aliases:
|
|
full = deprecated_aliases[command]
|
|
console.print(f"[red]❌ Alias '{command}' was removed.[/red]")
|
|
console.print(f"[yellow]💡 Use: ./binder {full} ...[/yellow]")
|
|
return False
|
|
|
|
if command in ("html", "pdf", "epub"):
|
|
return self.handle_format_reset_command(command, command_args)
|
|
|
|
if command in commands:
|
|
try:
|
|
return commands[command](command_args)
|
|
except KeyboardInterrupt:
|
|
console.print("\n[yellow]👋 Goodbye![/yellow]")
|
|
return False
|
|
except Exception as e:
|
|
console.print(f"[red]❌ Error: {e}[/red]")
|
|
return False
|
|
else:
|
|
console.print(f"[red]❌ Unknown command: {command}[/red]")
|
|
console.print("[yellow]💡 Use './binder help' to see available commands[/yellow]")
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
# Check for global flags
|
|
args = sys.argv[1:]
|
|
verbose = False
|
|
open_after = False
|
|
|
|
if "-v" in args:
|
|
verbose = True
|
|
args.remove("-v")
|
|
elif "--verbose" in args:
|
|
verbose = True
|
|
args.remove("--verbose")
|
|
|
|
if "-o" in args:
|
|
open_after = True
|
|
args.remove("-o")
|
|
elif "--open" in args:
|
|
open_after = True
|
|
args.remove("--open")
|
|
|
|
cli = MLSysBookCLI(verbose=verbose, open_after=open_after)
|
|
success = cli.run(args)
|
|
sys.exit(0 if success else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|