oxide_cli/addons/steps/
inject.rs1use std::path::Path;
2
3use anyhow::{Result, anyhow};
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> = String::from_utf8_lossy(&original)
24 .lines()
25 .map(str::to_string)
26 .collect();
27
28 let marker = step.after.as_deref().or(step.before.as_deref());
29
30 if let Some(marker) = marker {
31 match file_lines.iter().position(|l| l.contains(marker)) {
32 Some(idx) => {
33 let insert_idx = if step.after.is_some() { idx + 1 } else { idx };
34 for (i, line) in rendered.iter().enumerate() {
35 file_lines.insert(insert_idx + i, line.clone());
36 }
37 }
38 None => match step.if_not_found {
39 IfNotFound::Skip => continue,
40 IfNotFound::Error => {
41 return Err(anyhow!(
42 "Marker {:?} not found in {}",
43 marker,
44 path.display()
45 ));
46 }
47 IfNotFound::WarnAndAsk => {
48 eprintln!(
49 "Warning: marker {:?} not found in {}",
50 marker,
51 path.display()
52 );
53 let choice =
54 Select::new("How would you like to proceed?", vec!["Continue", "Abort"]).prompt()?;
55 if choice == "Abort" {
56 return Err(anyhow!("Aborted by user"));
57 }
58 continue;
59 }
60 },
61 }
62 } else {
63 let mut new_lines = rendered.clone();
64 new_lines.extend(file_lines);
65 file_lines = new_lines;
66 }
67
68 rollbacks.push(Rollback::RestoreFile {
69 path: path.clone(),
70 original,
71 });
72 std::fs::write(&path, file_lines.join("\n"))?;
73 }
74
75 Ok(rollbacks)
76}