mod edit;
mod handler;
mod plain;
mod render;
mod types;
use anyhow::Result;
use rusqlite::Connection;
use crate::infrastructure::config::Config;
use crate::infrastructure::db;
use crate::infrastructure::tui;
use edit::edit_loop;
use handler::load_detail;
use plain::{RenderOpts, render_markdown, render_plain};
pub fn guide_value(conn: &Connection, id_or_uuid: &str) -> Result<serde_json::Value> {
let task = db::resolve_task(conn, id_or_uuid)?;
let mut guide = db::guide_json(conn, &task.uuid)?;
let head = db::get_project(conn, &task.project)
.ok()
.flatten()
.and_then(|p| p.path)
.and_then(|path| crate::infrastructure::git::head_commit(std::path::Path::new(&path)));
let validated = db::get_guide_fields(conn, &task.uuid)?.validated_commit;
let stale = match (&head, &validated) {
(Some(h), Some(v)) => h != v,
_ => false,
};
let feedback = db::get_open_feedback(conn, &task.uuid)?;
let open_feedback: Vec<_> = feedback
.iter()
.map(|f| {
serde_json::json!({
"id": f.id,
"text": f.text,
"target_kind": f.target_kind,
"target_id": f.target_id,
"request_revision": f.request_revision,
})
})
.collect();
let needs_revision = feedback.iter().any(|f| f.request_revision);
if let Some(obj) = guide.as_object_mut() {
obj.insert(
"freshness".to_string(),
serde_json::json!({ "head": head, "validated_commit": validated, "stale": stale }),
);
obj.insert(
"open_feedback".to_string(),
serde_json::Value::Array(open_feedback),
);
obj.insert(
"needs_revision".to_string(),
serde_json::Value::Bool(needs_revision),
);
let description = obj
.get("description")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let tags: Vec<String> = obj
.get("tags")
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|x| x.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
let similar =
db::find_similar_strong_memories(conn, &description, &tags).unwrap_or_default();
if !similar.is_empty() {
let similar_json: Vec<serde_json::Value> = similar
.iter()
.map(|item| {
let label = format!(
"{}{}",
item.kind.chars().next().unwrap_or('m'),
item.display_id.unwrap_or(0)
);
let snippet: String = item
.summary
.clone()
.unwrap_or_else(|| item.body.clone())
.chars()
.take(160)
.collect();
serde_json::json!({
"label": label,
"description": item.title,
"snippet": snippet,
"files": item.files,
})
})
.collect();
obj.insert(
"similar_work".to_string(),
serde_json::Value::Array(similar_json),
);
}
}
Ok(guide)
}
pub fn run_json(conn: &Connection, _cfg: &Config, id_or_uuid: &str) -> Result<()> {
println!(
"{}",
serde_json::to_string_pretty(&guide_value(conn, id_or_uuid)?)?
);
Ok(())
}
pub fn run(
conn: &Connection,
cfg: &Config,
id_or_uuid: &str,
plain: bool,
md: bool,
history: bool,
) -> Result<()> {
let task = db::resolve_task(conn, id_or_uuid)?;
let detail = load_detail(conn, cfg, task)?;
let opts = RenderOpts { history };
if md {
print!("{}", render_markdown(&detail, opts));
return Ok(());
}
use std::io::IsTerminal;
if plain || !std::io::stdout().is_terminal() {
print!("{}", render_plain(&detail, opts));
return Ok(());
}
let mut terminal = tui::init_terminal()?;
let result = edit_loop(&mut terminal, conn, cfg, detail);
tui::restore_terminal()?;
result.map(|_| ())
}