feat: improve test harness output with structured colored logging

This commit is contained in:
mow 2026-03-08 09:04:28 +01:00
parent e928c61107
commit 31e7e9c3ef

View file

@ -4,44 +4,87 @@
// CLI test harness for the noctua_core document foundation. // CLI test harness for the noctua_core document foundation.
use clap::{ArgGroup, Parser}; use clap::{ArgGroup, Parser};
use noctua_core::document::manager::{DocumentCommand, DocumentManager, DocumentState}; use noctua_core::document::command::SessionCommand;
use noctua_core::document::session::SessionKind; use noctua_core::document::manager::{DocumentManager, DocumentState};
use noctua_core::document::session::CollectionKind;
use std::path::PathBuf; 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) { fn print_session(manager: &DocumentManager, session_name: &str) {
if let Some(session) = manager.session(session_name) { 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!( println!(
" Items: {}, current_index: {:?}", " {MAGENTA}Collection: {BOLD}{}{RESET} {DIM}({kind_label}, {} items, index: {:?}){RESET}",
session.name,
session.items.len(), session.items.len(),
session.current_index session.current_index
); );
for (i, item) in session.items.iter().enumerate() { for (i, item) in session.items.iter().enumerate() {
let marker = if session.current_index == Some(i) { let marker = if session.current_index == Some(i) {
"*" format!("{GREEN}=>{RESET}")
} else { } else {
" " format!("{DIM} {RESET}")
}; };
let name = item
.path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("?");
println!( println!(
" [{}] {} (page {})", " {marker} {DIM}[{i}]{RESET} {name} {DIM}(page {}){RESET}",
marker,
item.path.display(),
item.page_index item.page_index
); );
} }
} else { } 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); manager.handle(command);
match &manager.state { if let DocumentState::Error(e) = &manager.state {
DocumentState::Error(err) => { err(&format!("{e}"));
eprintln!("Error: {}", err); manager.state = DocumentState::Empty;
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"]) .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")] #[arg(long, value_name = "PATH")]
open_document: Option<PathBuf>, open_document: Option<PathBuf>,
@ -72,11 +116,15 @@ struct Cli {
fn main() { fn main() {
env_logger::init(); env_logger::init();
let cli = Cli::parse(); let cli = Opt::parse();
let mut manager = DocumentManager::new(); let mut mgr = DocumentManager::new();
// ── Open source ──
heading("Opening source");
if let Some(path) = cli.open_session { 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 { } else if let Some(path) = cli.open_document {
let dir = path let dir = path
.parent() .parent()
@ -89,22 +137,31 @@ fn main() {
.unwrap_or("Browse") .unwrap_or("Browse")
.to_string(); .to_string();
if manager.session(&session_name).is_none() { exec(
manager.handle(DocumentCommand::CreateSession { &mut mgr,
&format!("New session '{session_name}'"),
SessionCommand::New {
name: session_name.clone(), name: session_name.clone(),
kind: SessionKind::DirectoryBrowser, kind: CollectionKind::DirectoryBrowser,
path: Some(dir.clone()), },
}); );
manager.handle(DocumentCommand::ScanDirectory { exec(
session: session_name.clone(), &mut mgr,
dir, &format!("Select '{session_name}'"),
}); SessionCommand::Select {
} name: session_name.clone(),
},
manager.handle(DocumentCommand::AddFileToSession { );
session: session_name, exec(
path, &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 { } else if let Some(path) = cli.open_directory {
let session_name = path let session_name = path
.file_name() .file_name()
@ -112,142 +169,156 @@ fn main() {
.unwrap_or("Browse") .unwrap_or("Browse")
.to_string(); .to_string();
if manager.session(&session_name).is_none() { exec(
manager.handle(DocumentCommand::CreateSession { &mut mgr,
&format!("New session '{session_name}'"),
SessionCommand::New {
name: session_name.clone(), name: session_name.clone(),
kind: SessionKind::DirectoryBrowser, kind: CollectionKind::DirectoryBrowser,
path: Some(path.clone()), },
}); );
manager.handle(DocumentCommand::ScanDirectory { exec(
session: session_name.clone(), &mut mgr,
dir: path, &format!("Select '{session_name}'"),
}); SessionCommand::Select {
} name: session_name.clone(),
},
manager.handle(DocumentCommand::SetActive(session_name)); );
exec(
&mut mgr,
&format!("Scan directory {}", path.display()),
SessionCommand::AddDirectory { dir: path },
);
} }
match &manager.state { // ── State summary ──
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.");
if let Some(name) = manager.active_session_name.clone() { heading("State after open");
if let Some(session) = manager.session(&name) {
println!(
"UI: Session '{}' ({:?}) has {} items, current index: {:?}",
session.name,
session.kind,
session.items.len(),
session.current_index
);
match session.kind { match &mgr.state {
SessionKind::DirectoryBrowser => { DocumentState::Empty => info("No document loaded"),
println!("UI: Display as directory browser (thumbnails in left panel)"); DocumentState::Loading => info("Loading…"),
} DocumentState::Error(e) => err(&format!("{e}")),
SessionKind::DocumentCollection => { DocumentState::Loaded(_) => ok("Document loaded"),
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");
}
}
} }
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");
} }