pub mod ai;
pub mod merge_section;
pub mod merge_toml;
pub mod merge_yaml;
pub mod overwrite;
pub mod script;
use std::sync::Arc;
use async_trait::async_trait;
use camino::{Utf8Path, Utf8PathBuf};
use tokio::sync::Semaphore;
use crate::ai::{AiAgent, Backend};
use crate::applied::Decision;
use crate::config::ProjectEntry;
use crate::error::Result;
use crate::manifest::{AiMode, FileSpec, HowMode};
use crate::template::TemplateHandle;
pub use ai::Ai;
pub use merge_section::MergeSection;
pub use merge_toml::MergeToml;
pub use merge_yaml::MergeYaml;
pub use overwrite::Overwrite;
pub use script::Script;
pub struct ActionContext<'a> {
pub project: &'a ProjectEntry,
pub pj_root: &'a Utf8Path,
pub template: &'a TemplateHandle,
pub spec: &'a FileSpec,
pub src_abs: Utf8PathBuf,
pub dst_abs: Utf8PathBuf,
pub rendered_body: String,
pub current_body: Option<String>,
pub vars: &'a toml::Table,
pub tera_ctx: &'a tera::Context,
pub agent: Option<Arc<dyn AiAgent>>,
pub agent_backend: Option<Backend>,
pub interactive: bool,
pub yes_all: bool,
pub ai_prompt: Option<&'a str>,
pub ai_mode_override: Option<AiMode>,
pub ai_sema: Arc<Semaphore>,
}
#[derive(Debug, Clone)]
pub struct ActionPlan {
pub kind: PlanKind,
pub diff: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlanKind {
Create,
Update,
Unchanged,
SkippedWhen,
SkippedOnce,
Diverged,
}
#[derive(Debug, Clone)]
pub struct ActionOutcome {
pub kind: OutcomeKind,
pub decision: Option<Decision>,
pub diff: Option<String>,
pub error: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutcomeKind {
Wrote,
Unchanged,
Skipped,
Failed,
}
#[async_trait]
pub trait ApplyMode: Send + Sync {
async fn plan(&self, ctx: &ActionContext<'_>) -> Result<ActionPlan>;
async fn execute(&self, ctx: &ActionContext<'_>, dry_run: bool) -> Result<ActionOutcome>;
}
pub fn for_how(how: HowMode) -> Box<dyn ApplyMode> {
match how {
HowMode::Overwrite => Box::new(Overwrite),
HowMode::MergeSection => Box::new(MergeSection),
HowMode::MergeToml => Box::new(MergeToml),
HowMode::MergeYaml => Box::new(MergeYaml),
HowMode::Ai => Box::new(Ai),
HowMode::Script => Box::new(Script),
}
}
pub(crate) fn unified_diff(before: &str, after: &str, label: &str) -> String {
use similar::TextDiff;
let diff = TextDiff::from_lines(before, after);
let mut out = String::new();
out.push_str(&format!("--- {label} (current)\n"));
out.push_str(&format!("+++ {label} (incoming)\n"));
for hunk in diff.unified_diff().iter_hunks() {
out.push_str(&format!("{hunk}"));
}
out
}