feat: improve test harness output with structured colored logging
This commit is contained in:
parent
e928c61107
commit
31e7e9c3ef
1 changed files with 240 additions and 169 deletions
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue