324 lines
8.6 KiB
Rust
324 lines
8.6 KiB
Rust
// 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<PathBuf>,
|
||
|
||
/// Open a directory
|
||
#[arg(long, value_name = "PATH")]
|
||
open_directory: Option<PathBuf>,
|
||
|
||
/// Open a session file (.ron)
|
||
#[arg(long, value_name = "PATH")]
|
||
open_session: Option<PathBuf>,
|
||
}
|
||
|
||
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");
|
||
}
|