lean_ctx_patterns/
terraform.rs1macro_rules! static_regex {
4 ($pattern:expr_2021) => {{
5 static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
6 RE.get_or_init(|| {
7 regex::Regex::new($pattern).expect(concat!("BUG: invalid static regex: ", $pattern))
8 })
9 }};
10}
11
12fn plan_summary_re() -> &'static regex::Regex {
13 static_regex!(r"Plan:\s*(\d+)\s+to add,\s*(\d+)\s+to change,\s*(\d+)\s+to destroy")
14}
15
16fn apply_summary_re() -> &'static regex::Regex {
17 static_regex!(
18 r"Apply complete!\s*Resources:\s*(\d+)\s+added,\s*(\d+)\s+changed,\s*(\d+)\s+destroyed"
19 )
20}
21
22fn installed_provider_re() -> &'static regex::Regex {
23 static_regex!(r"-\s*Installed\s+([^\s]+)\s+v([0-9][^\s]*)")
24}
25
26fn provider_version_re() -> &'static regex::Regex {
27 static_regex!(r"\*\s*provider\[([^\]]+)\]\s+([0-9][^\s]*)")
28}
29
30fn is_provider_init_noise(line: &str) -> bool {
31 let t = line.trim_start();
32 let tl = t.to_ascii_lowercase();
33 tl.contains("initializing provider plugins")
34 || tl.contains("initializing the backend")
35 || tl.contains("finding ")
36 && (tl.contains("versions matching") || tl.contains("version of"))
37 || tl.starts_with("- finding ")
38 || tl.starts_with("- installing ")
39 || tl.contains("terraform init") && tl.contains("upgrade")
40 || tl.starts_with("╷")
41 || tl.starts_with("╵")
42 || tl.starts_with("│")
43}
44
45pub fn compress(command: &str, output: &str) -> Option<String> {
46 let c = command.trim();
47 let prefix = if c == "terraform" || c.starts_with("terraform ") {
48 "terraform"
49 } else if c == "tofu" || c.starts_with("tofu ") {
50 "tofu"
51 } else {
52 return None;
53 };
54 let sub = c.strip_prefix(prefix).map_or("", str::trim_start);
55 let sub_cmd = sub.split_whitespace().next().unwrap_or("");
56
57 match sub_cmd {
58 "plan" => Some(compress_plan(output)),
59 "apply" => Some(compress_apply(output)),
60 "init" => Some(compress_init(output)),
61 "validate" => Some(compress_validate(output)),
62 _ => Some(compress_generic(output)),
63 }
64}
65
66fn compress_plan(output: &str) -> String {
67 let mut kept = Vec::new();
68
69 for line in output.lines() {
70 if is_provider_init_noise(line) {
71 continue;
72 }
73 let tl = line.trim_start();
74 if tl.starts_with("- Installed ") || tl.starts_with("- Installing ") {
75 continue;
76 }
77
78 if let Some(caps) = plan_summary_re().captures(line) {
79 let add = caps.get(1).map_or("0", |m| m.as_str());
80 let chg = caps.get(2).map_or("0", |m| m.as_str());
81 let des = caps.get(3).map_or("0", |m| m.as_str());
82 kept.push(format!("+ {add} added, ~ {chg} changed, - {des} destroyed"));
83 continue;
84 }
85
86 let l = line.to_ascii_lowercase();
87 if l.contains("no changes.") || l.contains("infrastructure matches the configuration") {
88 kept.push("No changes.".to_string());
89 continue;
90 }
91
92 let is_diag = tl.contains('╷')
93 || tl.contains('│')
94 || tl.contains('╵')
95 || l.contains("error:")
96 || (l.contains("error ")
97 && (l.contains("terraform") || l.contains("plan") || l.contains("provider")))
98 || l.contains("warning:")
99 || l.contains("warning ");
100 if is_diag {
101 kept.push(line.trim().to_string());
102 }
103 }
104
105 if kept.is_empty() {
106 "terraform plan (no summary parsed)".to_string()
107 } else {
108 kept.join("\n")
109 }
110}
111
112fn compress_apply(output: &str) -> String {
113 let mut results = Vec::new();
114 let mut errors = Vec::new();
115
116 for line in output.lines() {
117 if is_provider_init_noise(line) {
118 continue;
119 }
120 let tl = line.trim();
121 if tl.is_empty() {
122 continue;
123 }
124
125 if let Some(caps) = apply_summary_re().captures(line) {
126 let a = caps.get(1).map_or("0", |m| m.as_str());
127 let c = caps.get(2).map_or("0", |m| m.as_str());
128 let d = caps.get(3).map_or("0", |m| m.as_str());
129 results.push(format!(
130 "Apply complete: +{a} added, ~{c} changed, -{d} destroyed"
131 ));
132 continue;
133 }
134
135 let ll = tl.to_ascii_lowercase();
136 if ll.contains("error")
137 && (ll.contains("apply") || ll.contains("terraform") || tl.contains('╷'))
138 {
139 errors.push(tl.to_string());
140 } else if ll.starts_with("creation complete")
141 || ll.starts_with("modification complete")
142 || ll.starts_with("destruction complete")
143 || ll.starts_with("destroy complete")
144 {
145 results.push(tl.to_string());
146 }
147 }
148
149 let mut out = Vec::new();
150 if !results.is_empty() {
151 out.push(results.join("\n"));
152 }
153 if !errors.is_empty() {
154 out.push(format!("errors:\n{}", errors.join("\n")));
155 }
156 if out.is_empty() {
157 "terraform apply (no summary parsed)".to_string()
158 } else {
159 out.join("\n\n")
160 }
161}
162
163fn compress_init(output: &str) -> String {
164 let mut providers: Vec<String> = Vec::new();
165 let mut success = false;
166
167 for line in output.lines() {
168 let tl = line.trim();
169 if tl.is_empty() {
170 continue;
171 }
172 let ll = tl.to_ascii_lowercase();
173 if ll.contains("terraform has been successfully initialized")
174 || ll.contains("initialization complete")
175 {
176 success = true;
177 }
178 if let Some(caps) = installed_provider_re().captures(tl) {
179 let name = caps.get(1).map_or("?", |m| m.as_str());
180 let ver = caps.get(2).map_or("?", |m| m.as_str());
181 providers.push(format!("{name} v{ver}"));
182 continue;
183 }
184 if let Some(caps) = provider_version_re().captures(tl) {
185 let reg = caps.get(1).map_or("?", |m| m.as_str());
186 let ver = caps.get(2).map_or("?", |m| m.as_str());
187 providers.push(format!("{reg} {ver}"));
188 }
189 }
190
191 let status = if success {
192 "Terraform initialized"
193 } else {
194 "terraform init"
195 };
196
197 if providers.is_empty() {
198 status.to_string()
199 } else {
200 format!("{status}\n{}", providers.join(", "))
201 }
202}
203
204fn compress_validate(output: &str) -> String {
205 let mut errs = Vec::new();
206 for line in output.lines() {
207 let tl = line.trim();
208 if tl.is_empty() {
209 continue;
210 }
211 let ll = tl.to_ascii_lowercase();
212 if ll.contains("success!") && ll.contains("configuration is valid") {
213 return "Success".to_string();
214 }
215 if ll.contains("error") || tl.starts_with('╷') || tl.starts_with('│') {
216 errs.push(tl.to_string());
217 }
218 }
219 if errs.is_empty() {
220 "Success".to_string()
221 } else {
222 errs.join("\n")
223 }
224}
225
226fn compress_generic(output: &str) -> String {
227 let mut lines: Vec<String> = output
228 .lines()
229 .filter(|l| !is_provider_init_noise(l))
230 .map(|l| l.trim().to_string())
231 .filter(|l| !l.is_empty())
232 .collect();
233 if lines.len() > 40 {
234 let n = lines.len();
235 lines = lines.split_off(n - 25);
236 format!("... (truncated)\n{}", lines.join("\n"))
237 } else {
238 lines.join("\n")
239 }
240}