sara-tasks 2.4.0

Sara — folder-aware task manager
use anyhow::Result;
use rusqlite::Connection;

use crate::commands::shared::print_json;
use crate::infrastructure::config::Config;
use crate::infrastructure::db;
use crate::infrastructure::model::Task;
use crate::infrastructure::project::detect_current_project;

mod render;

fn resolve_filter(
    conn: &Connection,
    cfg: &Config,
    all: bool,
    project_filter: Option<&str>,
) -> Result<Option<String>> {
    if all {
        Ok(None)
    } else if let Some(p) = project_filter {
        Ok(Some(p.to_string()))
    } else {
        let (name, _) = detect_current_project(conn, cfg)?;
        Ok(Some(name))
    }
}

fn tasks_to_value(tasks: &[Task]) -> serde_json::Value {
    let arr: Vec<_> = tasks
        .iter()
        .map(|t| {
            serde_json::json!({
                "id": t.id,
                "uuid": t.uuid.to_string(),
                "description": t.description,
                "project": t.project,
                "priority": t.priority.as_ref().map(|p| p.label()),
                "due": t.due.map(|d| d.to_rfc3339()),
                "urgency": t.urgency,
                "status": t.status.to_string(),
                "tags": t.tags,
            })
        })
        .collect();
    serde_json::json!({ "tasks": arr })
}

pub fn list_value(
    conn: &Connection,
    cfg: &Config,
    all: bool,
    project_filter: Option<&str>,
) -> Result<serde_json::Value> {
    let filter = resolve_filter(conn, cfg, all, project_filter)?;
    let tasks = db::list_tasks(conn, filter.as_deref())?;
    Ok(tasks_to_value(&tasks))
}

pub fn run(
    conn: &Connection,
    cfg: &Config,
    all: bool,
    project_filter: Option<&str>,
    as_json: bool,
    by_issue: bool,
) -> Result<()> {
    let no_color = std::env::var("NO_COLOR").is_ok();

    let filter = resolve_filter(conn, cfg, all, project_filter)?;

    let tasks = db::list_tasks(conn, filter.as_deref())?;
    let link_flags = db::link_flags_by_task(conn).unwrap_or_default();
    let github_synced = db::github_synced_task_uuids(conn).unwrap_or_default();
    let dep_info = db::dep_info_by_task(conn).unwrap_or_default();

    if as_json {
        print_json(&tasks_to_value(&tasks))?;
        return Ok(());
    }

    if by_issue {
        return render::print_by_issue(conn, &tasks, no_color);
    }

    if tasks.is_empty() {
        let scope = filter
            .as_deref()
            .map(|p| format!("project '{p}'"))
            .unwrap_or_else(|| "any project".to_string());
        println!("No pending tasks for {scope}.");
        if !all && filter.is_some() {
            let all_tasks = db::list_tasks(conn, None)?;
            if !all_tasks.is_empty() {
                println!(
                    "Tip: run `sara list -a` to see all {} pending tasks.",
                    all_tasks.len()
                );
            }
        }
        return Ok(());
    }

    render::print_table(
        &tasks,
        &link_flags,
        &github_synced,
        &dep_info,
        filter.as_deref(),
        no_color,
    );

    Ok(())
}