// SPDX-License-Identifier: GPL-3.0-or-later // examples/cli.rs // // CLI test harness for the noctua_core document foundation. use clap::{ArgGroup, Parser}; 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) { let kind_label = match session.kind { CollectionKind::DirectoryBrowser => "DirectoryBrowser", CollectionKind::DocumentCollection => "DocumentCollection", }; println!( " {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!( " {marker} {DIM}[{i}]{RESET} {name} {DIM}(page {}){RESET}", item.page_index ); } } else { err(&format!("Session '{session_name}' not found")); } } fn exec(manager: &mut DocumentManager, label: &str, command: SessionCommand) { step(label); manager.handle(command); 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)); } } #[derive(Parser, Debug)] #[command( name = "noctua", about = "A document viewer for the COSMIC desktop", group( ArgGroup::new("source") .required(true) .args(["open_document", "open_directory", "open_session"]) ) )] struct Opt { /// Open a document (raster, vector, portable) #[arg(long, value_name = "PATH")] open_document: Option, /// Open a directory #[arg(long, value_name = "PATH")] open_directory: Option, /// Open a session file (.ron) #[arg(long, value_name = "PATH")] open_session: Option, } fn main() { env_logger::init(); let cli = Opt::parse(); let mut mgr = DocumentManager::new(); // ── Open source ── heading("Opening source"); if let Some(path) = cli.open_session { exec(&mut mgr, "Open session file", SessionCommand::Open { path }); } else if let Some(path) = cli.open_document { let dir = path .parent() .map(|p| p.to_path_buf()) .unwrap_or_else(|| PathBuf::from(".")); let session_name = dir .file_name() .and_then(|n| n.to_str()) .unwrap_or("Browse") .to_string(); exec( &mut mgr, &format!("New session '{session_name}'"), SessionCommand::New { name: session_name.clone(), 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() .and_then(|n| n.to_str()) .unwrap_or("Browse") .to_string(); exec( &mut mgr, &format!("New session '{session_name}'"), SessionCommand::New { name: session_name.clone(), 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 }, ); } // ── State summary ── heading("State after open"); 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"); }