cartulary 0.3.0-alpha.1

The knowledge layer of your project — decisions, issues, docs, all in one place.
Documentation
//! Generic `body` command — argument schema and rendering shared by
//! issues and decision records.
//!
//! Two verbs: `show` (read) and `edit` (rewrite from stdin or `$EDITOR`).

use clap::{Arg, Command};

use crate::infra::driving::cli::errors::{die1, CliError};
use crate::infra::driving::cli::output::OutputFormat;
use crate::infra::driving::cli::render_structured;
use crate::infra::driving::cli::theme;

pub(crate) fn subcommand(noun: &'static str, id_help: &'static str) -> Command {
    let article = super::indefinite(noun);
    Command::new("body")
        .about(format!("Read or edit the body of {article}"))
        .subcommand_required(true)
        .arg_required_else_help(true)
        .subcommand(
            Command::new("show")
                .about(format!("Print the current body of {article}"))
                .arg(id_arg(id_help)),
        )
        .subcommand(
            Command::new("edit")
                .about(format!(
                    "Rewrite the body of {article} from stdin or $EDITOR",
                ))
                .arg(id_arg(id_help)),
        )
}

fn id_arg(help: &'static str) -> Arg {
    Arg::new("id").required(true).value_name("ID").help(help)
}

pub(crate) enum View {
    NotFound,
    Shown { body: String },
    Edited,
    EditNoOp,
}

pub(crate) fn render(
    view: View,
    display_id: &str,
    canonical_id: &str,
    noun: &str,
    output_fmt: OutputFormat,
) {
    match view {
        View::NotFound => die1(
            CliError::not_found(format!("{noun} '{display_id}'"), &format!("{noun} list")),
            output_fmt,
        ),
        view if output_fmt.is_structured() => {
            render_structured_view(view, canonical_id, output_fmt)
        }
        View::Shown { body } => print!("{body}"),
        View::Edited => println!(
            "{}",
            theme::success(&format!("Updated body of {noun} {display_id}")),
        ),
        View::EditNoOp => println!(
            "{}",
            theme::noop(&format!(
                "No change: body of {noun} {display_id} is unchanged",
            )),
        ),
    }
}

fn render_structured_view(view: View, canonical_id: &str, output_fmt: OutputFormat) {
    #[derive(serde::Serialize)]
    #[serde(untagged)]
    enum BodyResult<'a> {
        Show { id: &'a str, body: &'a str },
        Outcome { id: &'a str, outcome: &'a str },
    }
    let r = match &view {
        View::Shown { body } => BodyResult::Show {
            id: canonical_id,
            body,
        },
        View::Edited => BodyResult::Outcome {
            id: canonical_id,
            outcome: "updated",
        },
        View::EditNoOp => BodyResult::Outcome {
            id: canonical_id,
            outcome: "noop",
        },
        View::NotFound => unreachable!(),
    };
    render_structured(&r, output_fmt);
}