qctl 0.5.4

Control in-repo YAML work queues
use std::path::Path;

use anyhow::{Result, bail, ensure};

use crate::cli::{EditArgs, UnsetField};
use crate::document::Change;

pub(super) struct ListEdit<'a> {
    key: &'static str,
    before: &'a [String],
    after: Vec<String>,
}

impl<'a> ListEdit<'a> {
    pub(super) fn new(key: &'static str, before: &'a [String], after: Vec<String>) -> Self {
        Self { key, before, after }
    }
}

pub(super) fn changes(args: &EditArgs, path: &Path, lists: &[ListEdit<'_>]) -> Result<Vec<Change>> {
    if args.patch.is_some() && args.unset.contains(&UnsetField::Patch) {
        bail!("use --patch or --unset patch, not both");
    }
    if args.plan.is_some() && args.unset.contains(&UnsetField::Plan) {
        bail!("use --plan or --unset plan, not both");
    }
    crate::mutate::require_plan(path, args.plan.as_deref())?;

    let mut changes = Vec::new();
    push_scalar(&mut changes, "title", args.title.as_deref())?;
    push_scalar(&mut changes, "scope", args.scope.as_deref())?;
    push_scalar(&mut changes, "outcome", args.outcome.as_deref())?;
    if let Some(kind) = args.kind {
        changes.push(Change::set("kind", kind.to_string()));
    }
    push_scalar(&mut changes, "open", args.open.as_deref())?;
    push_optional(
        &mut changes,
        "patch",
        args.patch.as_deref(),
        args.unset.contains(&UnsetField::Patch),
    )?;
    push_optional(
        &mut changes,
        "plan",
        args.plan.as_deref(),
        args.unset.contains(&UnsetField::Plan),
    )?;
    for list in lists {
        push_list(&mut changes, list);
    }
    Ok(changes)
}

fn push_scalar(changes: &mut Vec<Change>, key: &str, value: Option<&str>) -> Result<()> {
    let Some(value) = value else {
        return Ok(());
    };
    ensure!(!value.is_empty(), "--{key} needs a value");
    changes.push(Change::set(key, value));
    Ok(())
}

fn push_optional(
    changes: &mut Vec<Change>,
    key: &str,
    value: Option<&str>,
    unset: bool,
) -> Result<()> {
    if unset {
        changes.push(Change::unset(key));
        return Ok(());
    }
    push_scalar(changes, key, value)
}

fn push_list(changes: &mut Vec<Change>, list: &ListEdit<'_>) {
    if list.before == list.after.as_slice() {
        return;
    }
    changes.push(Change::List {
        key: list.key.to_owned(),
        before: list.before.to_vec(),
        after: list.after.clone(),
    });
}