oxide_cli/addons/steps/
inject.rs1use std::path::Path;
2
3use anyhow::{anyhow, Result};
4use inquire::Select;
5
6use crate::addons::manifest::{IfNotFound, InjectStep};
7
8use super::{Rollback, render_lines, resolve_target};
9
10pub fn execute_inject(
11 step: &InjectStep,
12 project_root: &Path,
13 ctx: &tera::Context,
14) -> Result<Vec<Rollback>> {
15 let paths = resolve_target(&step.target, project_root)?;
16 let lines: Vec<String> = step.content.lines().map(str::to_string).collect();
17 let rendered = render_lines(&lines, ctx)?;
18
19 let mut rollbacks = Vec::new();
20
21 for path in paths {
22 let original = std::fs::read(&path)?;
23 let mut file_lines: Vec<String> =
24 String::from_utf8_lossy(&original).lines().map(str::to_string).collect();
25
26 let marker = step.after.as_deref().or(step.before.as_deref());
27
28 if let Some(marker) = marker {
29 match file_lines.iter().position(|l| l.contains(marker)) {
30 Some(idx) => {
31 let insert_idx = if step.after.is_some() { idx + 1 } else { idx };
32 for (i, line) in rendered.iter().enumerate() {
33 file_lines.insert(insert_idx + i, line.clone());
34 }
35 }
36 None => match step.if_not_found {
37 IfNotFound::Skip => continue,
38 IfNotFound::Error => {
39 return Err(anyhow!("Marker {:?} not found in {}", marker, path.display()));
40 }
41 IfNotFound::WarnAndAsk => {
42 eprintln!("Warning: marker {:?} not found in {}", marker, path.display());
43 let choice =
44 Select::new("How would you like to proceed?", vec!["Continue", "Abort"]).prompt()?;
45 if choice == "Abort" {
46 return Err(anyhow!("Aborted by user"));
47 }
48 continue;
49 }
50 },
51 }
52 } else {
53 let mut new_lines = rendered.clone();
54 new_lines.extend(file_lines);
55 file_lines = new_lines;
56 }
57
58 rollbacks.push(Rollback::RestoreFile { path: path.clone(), original });
59 std::fs::write(&path, file_lines.join("\n"))?;
60 }
61
62 Ok(rollbacks)
63}