diff --git a/core/examples/cli.rs b/core/examples/cli.rs index fcdde9f..4d45b5c 100644 --- a/core/examples/cli.rs +++ b/core/examples/cli.rs @@ -4,44 +4,87 @@ // CLI test harness for the noctua_core document foundation. use clap::{ArgGroup, Parser}; -use noctua_core::document::manager::{DocumentCommand, DocumentManager, DocumentState}; -use noctua_core::document::session::SessionKind; +use noctua_core::document::command::SessionCommand; +use noctua_core::document::manager::{DocumentManager, DocumentState}; +use noctua_core::document::session::CollectionKind; use std::path::PathBuf; +// ── ANSI helpers ── + +const RESET: &str = "\x1b[0m"; +const BOLD: &str = "\x1b[1m"; +const DIM: &str = "\x1b[2m"; +const GREEN: &str = "\x1b[32m"; +const YELLOW: &str = "\x1b[33m"; +const RED: &str = "\x1b[31m"; +const CYAN: &str = "\x1b[36m"; +const MAGENTA: &str = "\x1b[35m"; + +fn heading(text: &str) { + println!("\n{BOLD}{CYAN}── {text} ──{RESET}"); +} + +fn step(text: &str) { + println!(" {DIM}>{RESET} {text}"); +} + +fn ok(text: &str) { + println!(" {GREEN}Ok{RESET} {text}"); +} + +fn err(text: &str) { + eprintln!(" {RED}X{RESET} {text}"); +} + +fn info(text: &str) { + println!(" {YELLOW}i{RESET} {text}"); +} + fn print_session(manager: &DocumentManager, session_name: &str) { if let Some(session) = manager.session(session_name) { - println!("Session '{}' ({:?})", session.name, session.kind); + let kind_label = match session.kind { + CollectionKind::DirectoryBrowser => "DirectoryBrowser", + CollectionKind::DocumentCollection => "DocumentCollection", + }; println!( - " Items: {}, current_index: {:?}", + " {MAGENTA}Collection: {BOLD}{}{RESET} {DIM}({kind_label}, {} items, index: {:?}){RESET}", + session.name, session.items.len(), session.current_index ); for (i, item) in session.items.iter().enumerate() { let marker = if session.current_index == Some(i) { - "*" + format!("{GREEN}=>{RESET}") } else { - " " + format!("{DIM} {RESET}") }; + let name = item + .path + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or("?"); println!( - " [{}] {} (page {})", - marker, - item.path.display(), + " {marker} {DIM}[{i}]{RESET} {name} {DIM}(page {}){RESET}", item.page_index ); } } else { - println!("Session '{}' not found", session_name); + err(&format!("Session '{session_name}' not found")); } } -fn handle_command(manager: &mut DocumentManager, command: DocumentCommand) { +fn exec(manager: &mut DocumentManager, label: &str, command: SessionCommand) { + step(label); manager.handle(command); - match &manager.state { - DocumentState::Error(err) => { - eprintln!("Error: {}", err); - manager.state = DocumentState::Empty; - } - _ => {} + if let DocumentState::Error(e) = &manager.state { + err(&format!("{e}")); + manager.state = DocumentState::Empty; + } +} + +fn print_nav_index(manager: &DocumentManager, label: &str) { + if let Some(session) = manager.active_session() { + ok(&format!("{label:<28} → index {:?}", session.current_index)); } } @@ -55,8 +98,9 @@ fn handle_command(manager: &mut DocumentManager, command: DocumentCommand) { .args(["open_document", "open_directory", "open_session"]) ) )] -struct Cli { - /// Open a document (Raster, Vector, Portable) + +struct Opt { + /// Open a document (raster, vector, portable) #[arg(long, value_name = "PATH")] open_document: Option, @@ -72,11 +116,15 @@ struct Cli { fn main() { env_logger::init(); - let cli = Cli::parse(); - let mut manager = DocumentManager::new(); + let cli = Opt::parse(); + let mut mgr = DocumentManager::new(); + + // ── Open source ── + + heading("Opening source"); if let Some(path) = cli.open_session { - manager.handle(DocumentCommand::OpenSession(path)); + exec(&mut mgr, "Open session file", SessionCommand::Open { path }); } else if let Some(path) = cli.open_document { let dir = path .parent() @@ -89,22 +137,31 @@ fn main() { .unwrap_or("Browse") .to_string(); - if manager.session(&session_name).is_none() { - manager.handle(DocumentCommand::CreateSession { + exec( + &mut mgr, + &format!("New session '{session_name}'"), + SessionCommand::New { name: session_name.clone(), - kind: SessionKind::DirectoryBrowser, - path: Some(dir.clone()), - }); - manager.handle(DocumentCommand::ScanDirectory { - session: session_name.clone(), - dir, - }); - } - - manager.handle(DocumentCommand::AddFileToSession { - session: session_name, - path, - }); + kind: CollectionKind::DirectoryBrowser, + }, + ); + exec( + &mut mgr, + &format!("Select '{session_name}'"), + SessionCommand::Select { + name: session_name.clone(), + }, + ); + exec( + &mut mgr, + &format!("Scan directory {}", dir.display()), + SessionCommand::AddDirectory { dir }, + ); + exec( + &mut mgr, + &format!("Add document {}", path.display()), + SessionCommand::AddDocument { path }, + ); } else if let Some(path) = cli.open_directory { let session_name = path .file_name() @@ -112,142 +169,156 @@ fn main() { .unwrap_or("Browse") .to_string(); - if manager.session(&session_name).is_none() { - manager.handle(DocumentCommand::CreateSession { + exec( + &mut mgr, + &format!("New session '{session_name}'"), + SessionCommand::New { name: session_name.clone(), - kind: SessionKind::DirectoryBrowser, - path: Some(path.clone()), - }); - manager.handle(DocumentCommand::ScanDirectory { - session: session_name.clone(), - dir: path, - }); - } - - manager.handle(DocumentCommand::SetActive(session_name)); + kind: CollectionKind::DirectoryBrowser, + }, + ); + exec( + &mut mgr, + &format!("Select '{session_name}'"), + SessionCommand::Select { + name: session_name.clone(), + }, + ); + exec( + &mut mgr, + &format!("Scan directory {}", path.display()), + SessionCommand::AddDirectory { dir: path }, + ); } - match &manager.state { - DocumentState::Empty => println!("UI: No document loaded."), - DocumentState::Loading => println!("UI: Loading..."), - DocumentState::Error(err) => eprintln!("UI: Error -> {err}"), - DocumentState::Loaded(_) => { - println!("UI: Document loaded."); + // ── State summary ── - if let Some(name) = manager.active_session_name.clone() { - if let Some(session) = manager.session(&name) { - println!( - "UI: Session '{}' ({:?}) has {} items, current index: {:?}", - session.name, - session.kind, - session.items.len(), - session.current_index - ); + heading("State after open"); - match session.kind { - SessionKind::DirectoryBrowser => { - println!("UI: Display as directory browser (thumbnails in left panel)"); - } - SessionKind::DocumentCollection => { - println!("UI: Display as document collection (pages)"); - } - } - } - - println!("\nUI: Test navigation..."); - handle_command(&mut manager, DocumentCommand::NavigateNext); - if let Some(session) = manager.session(&name) { - println!( - "UI: After NavigateNext -> index {:?}", - session.current_index - ); - } - - handle_command(&mut manager, DocumentCommand::NavigatePrevious); - if let Some(session) = manager.session(&name) { - println!( - "UI: After NavigatePrevious -> index {:?}", - session.current_index - ); - } - - handle_command(&mut manager, DocumentCommand::SelectItem(0)); - if let Some(session) = manager.session(&name) { - println!( - "UI: After SelectItem(0) -> index {:?}", - session.current_index - ); - } - - println!("\nUI: Test copy/move/delete..."); - handle_command( - &mut manager, - DocumentCommand::CreateSession { - name: "Favorites".to_string(), - kind: SessionKind::DocumentCollection, - path: None, - }, - ); - handle_command( - &mut manager, - DocumentCommand::CreateSession { - name: "Workspace".to_string(), - kind: SessionKind::DocumentCollection, - path: None, - }, - ); - - println!("\nBefore CopyItemToSession:"); - print_session(&manager, "Favorites"); - - handle_command( - &mut manager, - DocumentCommand::CopyItemToSession { - source_session: name.clone(), - target_session: "Favorites".to_string(), - source_item_index: 0, - target_item_index: None, - }, - ); - println!("\nAfter CopyItemToSession to Favorites:"); - print_session(&manager, "Favorites"); - - println!("\n--- Test DeleteItemFromSession on DirectoryBrowser (should fail) ---"); - handle_command( - &mut manager, - DocumentCommand::DeleteItemFromSession { - session: name.clone(), - item_index: None, - }, - ); - - println!("\n--- Test Move from Favorites to Workspace ---"); - println!("\nBefore Move:"); - print_session(&manager, "Favorites"); - print_session(&manager, "Workspace"); - - handle_command( - &mut manager, - DocumentCommand::CopyItemToSession { - source_session: "Favorites".to_string(), - target_session: "Workspace".to_string(), - source_item_index: 0, - target_item_index: None, - }, - ); - - handle_command( - &mut manager, - DocumentCommand::DeleteItemFromSession { - session: "Favorites".to_string(), - item_index: Some(0), - }, - ); - - println!("\nAfter Move:"); - print_session(&manager, "Favorites"); - print_session(&manager, "Workspace"); - } - } + match &mgr.state { + DocumentState::Empty => info("No document loaded"), + DocumentState::Loading => info("Loading…"), + DocumentState::Error(e) => err(&format!("{e}")), + DocumentState::Loaded(_) => ok("Document loaded"), } + + let active_name = match mgr.active_session_name.clone() { + Some(n) => n, + None => { + info("No active session – nothing more to test."); + return; + } + }; + + print_session(&mgr, &active_name); + + // ── Navigation ── + + heading("Navigation"); + + exec(&mut mgr, "NextDocument", SessionCommand::NextDocument); + print_nav_index(&mgr, "After NextDocument"); + + exec( + &mut mgr, + "PreviousDocument", + SessionCommand::PreviousDocument, + ); + print_nav_index(&mgr, "After PreviousDocument"); + + exec( + &mut mgr, + "SelectDocument { index: 0 }", + SessionCommand::SelectDocument { index: 0 }, + ); + print_nav_index(&mgr, "After SelectDocument(0)"); + + // ── Collection operations ── + + heading("Collection operations"); + + exec( + &mut mgr, + "New session 'Favorites'", + SessionCommand::New { + name: "Favorites".to_string(), + kind: CollectionKind::DocumentCollection, + }, + ); + exec( + &mut mgr, + "New session 'Workspace'", + SessionCommand::New { + name: "Workspace".to_string(), + kind: CollectionKind::DocumentCollection, + }, + ); + + // Copy first item from browser → Favorites + heading("Copy item to Favorites"); + step("Before:"); + print_session(&mgr, "Favorites"); + + exec( + &mut mgr, + &format!("Copy [0] from '{active_name}' → 'Favorites'"), + SessionCommand::CopyDocument { + source: active_name.clone(), + target: "Favorites".to_string(), + source_index: 0, + target_index: 0, + }, + ); + step("After:"); + print_session(&mgr, "Favorites"); + + // Remove from DirectoryBrowser (should fail) + heading("Remove from DirectoryBrowser (expect error)"); + exec( + &mut mgr, + &format!("Select '{active_name}'"), + SessionCommand::Select { + name: active_name.clone(), + }, + ); + exec( + &mut mgr, + "RemoveDocument { index: 0 }", + SessionCommand::RemoveDocument { index: 0 }, + ); + + // Move: Favorites → Workspace (copy + delete) + heading("Move: Favorites → Workspace"); + step("Before:"); + print_session(&mgr, "Favorites"); + print_session(&mgr, "Workspace"); + + exec( + &mut mgr, + "Copy [0] from 'Favorites' → 'Workspace'", + SessionCommand::CopyDocument { + source: "Favorites".to_string(), + target: "Workspace".to_string(), + source_index: 0, + target_index: 0, + }, + ); + exec( + &mut mgr, + "Select 'Favorites'", + SessionCommand::Select { + name: "Favorites".to_string(), + }, + ); + exec( + &mut mgr, + "RemoveDocument { index: 0 }", + SessionCommand::RemoveDocument { index: 0 }, + ); + + step("After:"); + print_session(&mgr, "Favorites"); + print_session(&mgr, "Workspace"); + + heading("Done"); }