- 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
113 lines
3.5 KiB
Rust
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);
|
|
}
|
|
}
|