aven 0.1.13

Local-first task manager CLI and sync server
Documentation
use anyhow::Result;
use aven_core::db::Database;
use serde::Serialize;

use crate::cli::{ProjectCommand, ProjectListArgs, ProjectPathSubcommand, ProjectSubcommand};
use crate::operations::{
    add_project_path_operation, create_project_operation, delete_project_operation,
    list_project_paths_operation, remove_project_path_operation, rename_project_operation,
};
use crate::render::{changed_text, quote};
use crate::workspaces::Workspace;

pub(crate) async fn cmd_project(
    database: &Database,
    workspace: &Workspace,
    args: ProjectCommand,
) -> Result<()> {
    match args.command {
        ProjectSubcommand::Create { name, path } => {
            let outcome =
                create_project_operation(database, workspace, &name, path.as_deref()).await?;
            let project = outcome.project;
            println!(
                "created-project {} prefix={} name={}",
                project.key,
                project.prefix,
                quote(&project.name)
            );
        }
        ProjectSubcommand::Delete { project } => {
            let outcome = delete_project_operation(database, workspace, &project).await?;
            println!(
                "deleted-project {} prefix={} name={}",
                outcome.project.key,
                outcome.project.prefix,
                quote(&outcome.project.name)
            );
        }
        ProjectSubcommand::List(args) => cmd_projects(database, workspace, args).await?,
        ProjectSubcommand::Rename {
            project,
            new_name,
            prefix,
        } => {
            let outcome = rename_project_operation(
                database,
                workspace,
                &project,
                &new_name,
                prefix.as_deref(),
            )
            .await?;
            println!(
                "renamed-project {} changed={} old={} old_prefix={} prefix={} name={}",
                outcome.project.key,
                changed_text(outcome.changed),
                outcome.previous.key,
                outcome.previous.prefix,
                outcome.project.prefix,
                quote(&outcome.project.name)
            );
            if outcome.changed && outcome.config_mapping {
                println!("updated-config-project-mapping {}", outcome.project.key);
            }
        }
        ProjectSubcommand::Path { command } => match command {
            ProjectPathSubcommand::Add { project, path } => {
                let outcome =
                    add_project_path_operation(database, workspace, &project, &path).await?;
                println!(
                    "added-project-path {} path={} config={}",
                    outcome.project.key,
                    quote(&outcome.path),
                    quote(&outcome.config_path.display().to_string())
                );
            }
            ProjectPathSubcommand::Remove { project, path } => {
                let outcome =
                    remove_project_path_operation(database, workspace, &project, &path).await?;
                println!(
                    "removed-project-path {} path={} config={}",
                    outcome.project.key,
                    quote(&outcome.path),
                    quote(&outcome.config_path.display().to_string())
                );
            }
            ProjectPathSubcommand::List { project } => {
                let paths =
                    list_project_paths_operation(database, workspace, project.as_deref()).await?;
                for item in paths {
                    println!("{} path={}", item.project.key, quote(&item.path));
                }
            }
        },
    }
    Ok(())
}

#[derive(Serialize)]
struct ProjectListJsonItem {
    key: String,
    name: String,
    prefix: String,
}

async fn cmd_projects(
    database: &Database,
    workspace: &Workspace,
    args: ProjectListArgs,
) -> Result<()> {
    let mut projects = database
        .list_projects(&workspace.id, args.search.as_deref())
        .await?;
    if let Some(limit) = args.limit {
        projects.truncate(limit);
    }
    if args.json {
        let items: Vec<ProjectListJsonItem> = projects
            .into_iter()
            .map(|p| ProjectListJsonItem {
                key: p.key,
                name: p.name,
                prefix: p.prefix,
            })
            .collect();
        serde_json::to_writer_pretty(std::io::stdout(), &items)?;
        println!();
    } else {
        for project in projects {
            println!(
                "{} prefix={} name={}",
                project.key,
                project.prefix,
                quote(&project.name)
            );
        }
    }
    Ok(())
}