use std::path::PathBuf;
use agileplus_sqlite::Storage;
use anyhow::{Context, Result};
use clap::{Args, Subcommand};
#[derive(Debug, Args)]
#[command(name = "note")]
pub struct NoteCommand {
#[arg(short, long)]
db: PathBuf,
#[arg(short, long)]
id: String,
#[command(subcommand)]
action: NoteAction,
}
#[derive(Debug, Subcommand)]
enum NoteAction {
Add {
#[arg(short, long)]
body: Option<String>,
#[arg(short = 'f', long = "file")]
file: Option<PathBuf>,
},
List,
Delete {
note_id: i64,
},
}
impl NoteCommand {
pub fn run(&self) -> Result<()> {
let storage = Storage::open(&self.db)
.with_context(|| format!("Failed to open storage at: {}", self.db.display()))?;
match &self.action {
NoteAction::Add { body, file } => {
let text = read_body(body.as_deref(), file.as_ref())?;
let id = storage
.add_note(&self.id, &text)
.with_context(|| format!("Failed to add note to graph {}", self.id))?;
println!("noted: {} -> note {id}", self.id);
}
NoteAction::List => {
let notes = storage
.list_notes(&self.id)
.with_context(|| format!("Failed to list notes for graph {}", self.id))?;
if notes.is_empty() {
println!("(no notes on {})", self.id);
return Ok(());
}
println!("{} note(s) on {}:", notes.len(), self.id);
for n in ¬es {
let preview: String = n.body.chars().take(80).collect();
println!(
" #{} [{}] {}",
n.id, n.created_at, preview
);
}
}
NoteAction::Delete { note_id } => {
let removed = storage
.delete_note(&self.id, *note_id)
.with_context(|| {
format!("Failed to delete note {note_id} on graph {}", self.id)
})?;
if removed {
println!("deleted note {note_id} on {}", self.id);
} else {
println!("no such note: {note_id} on {}", self.id);
}
}
}
Ok(())
}
}
fn read_body(body: Option<&str>, file: Option<&PathBuf>) -> Result<String> {
match (body, file) {
(Some(b), None) => Ok(b.to_string()),
(None, Some(path)) => {
if path == &PathBuf::from("-") {
let mut buf = String::new();
std::io::Read::read_to_string(&mut std::io::stdin(), &mut buf)
.context("reading note body from stdin")?;
Ok(buf)
} else {
std::fs::read_to_string(path)
.with_context(|| format!("reading note body from {}", path.display()))
}
}
(Some(_), Some(_)) => {
anyhow::bail!("provide either --body or --file, not both")
}
(None, None) => {
anyhow::bail!("provide --body <text> or --file <path> (use - for stdin)")
}
}
}