lean_ctx_patterns/
pulumi.rs1macro_rules! static_regex {
12 ($pattern:expr_2021) => {{
13 static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
14 RE.get_or_init(|| {
15 regex::Regex::new($pattern).expect(concat!("BUG: invalid static regex: ", $pattern))
16 })
17 }};
18}
19
20fn summary_re() -> &'static regex::Regex {
22 static_regex!(
23 r"^[+~\-]?\s*\d+\s+(to create|to update|to delete|to replace|created|updated|deleted|replaced|unchanged|changed)\b"
24 )
25}
26
27pub fn compress(command: &str, output: &str) -> Option<String> {
28 let c = command.trim();
29 let sub = c
30 .strip_prefix("pulumi")
31 .map_or("", str::trim_start)
32 .split_whitespace()
33 .next()
34 .unwrap_or("");
35 match sub {
36 "up" | "update" | "preview" | "destroy" | "refresh" => Some(compress_update(output)),
37 _ => Some(compress_generic(output)),
38 }
39}
40
41fn compress_update(output: &str) -> String {
42 let mut kept: Vec<String> = Vec::new();
43 let mut in_outputs = false;
44
45 for raw in output.lines() {
46 let t = raw.trim();
47 if t.is_empty() {
48 in_outputs = false;
49 continue;
50 }
51 let tl = t.to_ascii_lowercase();
52
53 if t == "Outputs:" {
54 in_outputs = true;
55 kept.push(t.to_string());
56 continue;
57 }
58 if in_outputs {
59 if t == "Resources:" || tl.starts_with("duration:") {
62 in_outputs = false;
63 } else {
64 kept.push(format!(" {t}"));
65 continue;
66 }
67 }
68
69 if t == "Resources:" || tl.starts_with("duration:") {
70 kept.push(t.to_string());
71 continue;
72 }
73 if summary_re().is_match(t) {
74 kept.push(t.to_string());
75 continue;
76 }
77 if tl.starts_with("updating (")
78 || tl.starts_with("previewing update (")
79 || tl.starts_with("destroying (")
80 {
81 kept.push(t.to_string());
82 continue;
83 }
84 if tl.contains("error:")
85 || tl.starts_with("error")
86 || tl.starts_with("diagnostics:")
87 || tl.contains("failed")
88 || tl.contains("panic:")
89 {
90 kept.push(t.to_string());
91 }
92 }
93
94 if kept.is_empty() {
95 return compress_generic(output);
96 }
97 kept.join("\n")
98}
99
100fn compress_generic(output: &str) -> String {
101 let lines: Vec<&str> = output
102 .lines()
103 .map(str::trim_end)
104 .filter(|l| !l.trim().is_empty())
105 .collect();
106 if lines.is_empty() {
107 return "pulumi: ok".to_string();
108 }
109 let max = 15;
110 if lines.len() <= max {
111 return lines.join("\n");
112 }
113 format!(
114 "{}\n... (+{} lines)",
115 lines[..max].join("\n"),
116 lines.len() - max
117 )
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 const UP: &str = "Updating (dev):\n Type Name Status\n + pulumi:pulumi:Stack proj-dev created\n + ├─ aws:s3:Bucket my-bucket created\n ~ └─ aws:s3:BucketPolicy pol updated\n\nOutputs:\n bucketName: \"my-bucket-abc123\"\n url : \"https://my-bucket.s3.amazonaws.com\"\n\nResources:\n + 5 created\n ~ 2 updated\n 10 unchanged\n\nDuration: 35s\n";
125
126 #[test]
127 fn keeps_outputs_summary_duration_drops_tree() {
128 let r = compress("pulumi up", UP).unwrap();
129 assert!(
130 r.contains("url : \"https://my-bucket"),
131 "keeps outputs: {r}"
132 );
133 assert!(r.contains("+ 5 created"), "keeps summary: {r}");
134 assert!(r.contains("Duration: 35s"), "keeps duration: {r}");
135 assert!(
136 !r.contains("pulumi:pulumi:Stack"),
137 "drops resource tree: {r}"
138 );
139 assert!(!r.contains("aws:s3:Bucket "), "drops resource tree: {r}");
140 }
141
142 #[test]
143 fn keeps_errors() {
144 let out = "Updating (dev):\n + aws:s3:Bucket b created\nDiagnostics:\n aws:s3:Bucket (b):\n error: creating S3 Bucket: BucketAlreadyExists";
145 let r = compress("pulumi up", out).unwrap();
146 assert!(r.contains("error: creating S3 Bucket"), "{r}");
147 }
148
149 #[test]
150 fn shorter_than_input() {
151 let r = compress("pulumi up", UP).unwrap();
152 assert!(r.len() < UP.len(), "compressed shorter");
153 }
154}