Files
RogueAgenda/src/main.rs
Chili Palmer c8ad487ca1 modularize date, database, and model code
- Add reusable date parsing, formatting, and recurrence modules
- Split database import helpers and model types into focused modules
- Expose the application as a library for the binary
2026-08-19 22:09:40 +02:00

113 lines
3.5 KiB
Rust

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<Preset>,
/// Import a text or iCalendar (.ics) file, then exit
#[arg(long)]
import: Option<PathBuf>,
/// Export as csv, json, markdown, html, or ics, then exit
#[arg(long, value_name = "FORMAT")]
export: Option<String>,
/// Destination for --export
#[arg(long)]
output: Option<PathBuf>,
/// Saved view to use for export (defaults to All Items)
#[arg(long, requires = "export")]
view: Option<String>,
}
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);
}
}