noctua/core/examples/cli.rs

397 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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::manager::{DocumentManager, DocumentState};
use noctua_core::document::session::command::SessionCommand;
use noctua_core::document::session::data::{CollectionKind, SessionData};
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(session: &SessionData, active_doc_index: Option<usize>) {
let kind_label = match session.kind {
CollectionKind::DirectoryBrowser => "DirectoryBrowser",
CollectionKind::DocumentCollection => "DocumentCollection",
};
println!(
" {MAGENTA}Session: {BOLD}{}{RESET} {DIM}({kind_label}, {} items, active: {:?}){RESET}",
session.name,
session.items.len(),
active_doc_index,
);
for (i, item) in session.items.iter().enumerate() {
let marker = if active_doc_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
);
}
}
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_active_session(mgr: &DocumentManager) {
if let Some(session) = mgr.active_session() {
print_session(session, mgr.active_document_index());
} else {
info("No active session");
}
}
#[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::OpenSession { 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::NewSession {
name: session_name.clone(),
},
);
exec(
&mut mgr,
&format!("Select '{session_name}'"),
SessionCommand::SelectSession {
name: session_name.clone(),
},
);
exec(
&mut mgr,
&format!("Add directory {}", dir.display()),
SessionCommand::AddDirectoryToSession { dir },
);
exec(
&mut mgr,
&format!("Add document {}", path.display()),
SessionCommand::AddDocumentToSession { 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::NewSession {
name: session_name.clone(),
},
);
exec(
&mut mgr,
&format!("Select '{session_name}'"),
SessionCommand::SelectSession {
name: session_name.clone(),
},
);
exec(
&mut mgr,
&format!("Add directory {}", path.display()),
SessionCommand::AddDirectoryToSession { 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().map(|s| s.name.clone()) {
Some(n) => n,
None => {
info("No active session nothing more to test.");
return;
}
};
print_active_session(&mgr);
// Navigation
heading("Navigation");
exec(&mut mgr, "NextDocument", SessionCommand::NextDocument);
ok(&format!(
"After NextDocument → index {:?}",
mgr.active_document_index()
));
exec(
&mut mgr,
"PreviousDocument",
SessionCommand::PreviousDocument,
);
ok(&format!(
"After PreviousDocument → index {:?}",
mgr.active_document_index()
));
exec(
&mut mgr,
"SelectDocument { index: 0 }",
SessionCommand::SelectDocument { index: 0 },
);
ok(&format!(
"After SelectDocument(0) → index {:?}",
mgr.active_document_index()
));
// Move operations
heading("Move operations");
step("Before MoveDocumentDown:");
print_active_session(&mgr);
exec(
&mut mgr,
"MoveDocumentDown",
SessionCommand::MoveDocumentDown,
);
ok(&format!(
"After MoveDocumentDown → index {:?}",
mgr.active_document_index()
));
exec(&mut mgr, "MoveDocumentUp", SessionCommand::MoveDocumentUp);
ok(&format!(
"After MoveDocumentUp → index {:?}",
mgr.active_document_index()
));
// Copy / Paste
heading("Copy / Paste");
exec(
&mut mgr,
"New session 'Favorites'",
SessionCommand::NewSession {
name: "Favorites".to_string(),
},
);
exec(
&mut mgr,
"New session 'Workspace'",
SessionCommand::NewSession {
name: "Workspace".to_string(),
},
);
step("Favorites before paste:");
if let Some(session) = mgr.session_by_name("Favorites") {
print_session(session, None);
}
// SelectSession source → SelectDocument → CopyDocument → SelectSession target → PasteDocument
exec(
&mut mgr,
&format!("SelectSession '{active_name}'"),
SessionCommand::SelectSession {
name: active_name.clone(),
},
);
exec(
&mut mgr,
"SelectDocument { index: 0 }",
SessionCommand::SelectDocument { index: 0 },
);
exec(&mut mgr, "CopyDocument", SessionCommand::CopyDocument);
exec(
&mut mgr,
"SelectSession 'Favorites'",
SessionCommand::SelectSession {
name: "Favorites".to_string(),
},
);
exec(&mut mgr, "PasteDocument", SessionCommand::PasteDocument);
step("Favorites after paste:");
print_active_session(&mgr);
// Move pasted item to front via MoveDocumentTo
exec(
&mut mgr,
"MoveDocumentTo { index: 0 }",
SessionCommand::MoveDocumentTo { index: 0 },
);
step("Favorites after MoveDocumentTo(0):");
print_active_session(&mgr);
// Remove active document
exec(&mut mgr, "RemoveDocument", SessionCommand::RemoveDocument);
step("Favorites after RemoveDocument:");
print_active_session(&mgr);
// Session operations
heading("Session operations");
exec(
&mut mgr,
&format!("SelectSession '{active_name}'"),
SessionCommand::SelectSession {
name: active_name.clone(),
},
);
exec(
&mut mgr,
"DuplicateSession 'Snapshot'",
SessionCommand::DuplicateSession {
new_name: "Snapshot".to_string(),
},
);
step("Snapshot session:");
if let Some(session) = mgr.session_by_name("Snapshot") {
print_session(session, None);
}
exec(
&mut mgr,
"SelectSession 'Workspace'",
SessionCommand::SelectSession {
name: "Workspace".to_string(),
},
);
exec(
&mut mgr,
"RenameSession 'Archive'",
SessionCommand::RenameSession {
name: "Archive".to_string(),
},
);
step("All sessions after rename:");
for session in mgr.sessions() {
let is_active = mgr
.active_session()
.map_or(false, |a| a.name == session.name);
let marker = if is_active {
format!("{GREEN}*{RESET}")
} else {
format!("{DIM} {RESET}")
};
println!(" {marker} {}", session.name);
}
exec(
&mut mgr,
"CloseSession (closes 'Archive')",
SessionCommand::CloseSession,
);
step("All sessions after close:");
for session in mgr.sessions() {
println!(" {DIM}-{RESET} {}", session.name);
}
heading("Done");
}