use std::{io::stdout, path::PathBuf, time::Duration}; use anyhow::{Context, Result}; use clap::Parser; use crossterm::{ event::{self, Event, KeyEventKind}, event::{DisableMouseCapture, EnableMouseCapture}, execute, terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, }; use ratatui::{Terminal, backend::CrosstermBackend}; use rogue_agenda::{App, Database, Preset, ui}; #[derive(Debug, Parser)] #[command( name = "rogue-agenda", version, about = "A category-driven personal information manager for the terminal" )] struct Args { /// Rogue Agenda SQLite document (the .agnd extension is added if absent) #[arg(default_value = "rogue.agnd")] document: PathBuf, /// Populate a new document with representative items #[arg(long)] demo: bool, /// Initialize a new document with a complete example workspace #[arg(long, value_enum, conflicts_with = "demo")] preset: Option, /// Import a text or iCalendar (.ics) file, then exit #[arg(long)] import: Option, /// Export as csv, json, markdown, html, or ics, then exit #[arg(long, value_name = "FORMAT")] export: Option, /// Destination for --export #[arg(long)] output: Option, /// Saved view to use for export (defaults to All Items) #[arg(long, requires = "export")] view: Option, } fn main() -> Result<()> { let args = Args::parse(); let mut path = args.document; if path.extension().is_none() { path.set_extension("agnd"); } let mut db = Database::open(&path)?; if args.demo { db.seed_demo()?; } if let Some(preset) = args.preset { db.apply_preset(preset)?; } if let Some(import) = args.import { let count = db.import_path(&import)?; println!("Imported {count} item(s) into {}", path.display()); return Ok(()); } if let Some(format) = args.export { let output = args.output.context("--output is required with --export")?; let count = db.export(&format, &output, args.view.as_deref())?; println!("Exported {count} item(s) to {}", output.display()); return Ok(()); } run_tui(App::new(db, path)?) } fn run_tui(mut app: App) -> Result<()> { enable_raw_mode()?; let _guard = TerminalGuard; let mut out = stdout(); execute!(out, EnterAlternateScreen, EnableMouseCapture)?; let backend = CrosstermBackend::new(out); let mut terminal = Terminal::new(backend)?; terminal.clear()?; let result = (|| -> Result<()> { loop { app.tick()?; terminal.draw(|f| ui::draw(f, &mut app))?; if app.should_quit { break; } if event::poll(Duration::from_millis(250))? { match event::read()? { Event::Key(k) if k.kind == KeyEventKind::Press => app.handle_key(k)?, Event::Mouse(m) => app.handle_mouse(m)?, Event::Resize(_, _) => {} _ => {} } } } app.shutdown()?; Ok(()) })(); terminal.show_cursor()?; if let Err(e) = result { return Err(e.context("Rogue Agenda session ended unexpectedly")); } Ok(()) } struct TerminalGuard; impl Drop for TerminalGuard { fn drop(&mut self) { let _ = disable_raw_mode(); let _ = execute!(stdout(), DisableMouseCapture, LeaveAlternateScreen); } }