cuenv_core/tasks/
captures.rs1use super::{CaptureSource, TaskCapture};
4use regex::Regex;
5use std::collections::HashMap;
6
7pub fn resolve_captures(
17 captures: &HashMap<String, TaskCapture>,
18 stdout: &str,
19 stderr: &str,
20) -> HashMap<String, String> {
21 let mut results = HashMap::new();
22
23 for (name, cap) in captures {
24 let source = match cap.source {
25 CaptureSource::Stdout => stdout,
26 CaptureSource::Stderr => stderr,
27 };
28
29 let re = match Regex::new(&cap.pattern) {
30 Ok(re) => re,
31 Err(err) => {
32 tracing::warn!(
33 capture = name,
34 pattern = cap.pattern,
35 error = %err,
36 "Failed to compile capture regex"
37 );
38 continue;
39 }
40 };
41
42 match re.captures(source).and_then(|caps| caps.get(1)) {
43 Some(m) => {
44 results.insert(name.clone(), m.as_str().trim().to_string());
45 }
46 None => {
47 tracing::debug!(
48 capture = name,
49 pattern = cap.pattern,
50 "No capture group 1 match found"
51 );
52 }
53 }
54 }
55
56 results
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 fn make_capture(pattern: &str, source: CaptureSource) -> TaskCapture {
64 TaskCapture {
65 pattern: pattern.to_string(),
66 source,
67 }
68 }
69
70 #[test]
71 fn test_resolve_captures_from_stdout() {
72 let captures = HashMap::from([(
73 "previewUrl".to_string(),
74 make_capture(r"Version Preview URL: (.+)", CaptureSource::Stdout),
75 )]);
76
77 let stdout = "Uploading...\nVersion Preview URL: https://example.workers.dev\nDone.";
78
79 let result = resolve_captures(&captures, stdout, "");
80 assert_eq!(
81 result.get("previewUrl").map(String::as_str),
82 Some("https://example.workers.dev")
83 );
84 }
85
86 #[test]
87 fn test_resolve_captures_from_stderr() {
88 let captures = HashMap::from([(
89 "errorCode".to_string(),
90 make_capture(r"Error code: (\d+)", CaptureSource::Stderr),
91 )]);
92
93 let result = resolve_captures(&captures, "", "Error code: 42\nFailed.");
94 assert_eq!(result.get("errorCode").map(String::as_str), Some("42"));
95 }
96
97 #[test]
98 fn test_resolve_captures_no_match() {
99 let captures = HashMap::from([(
100 "missing".to_string(),
101 make_capture(r"not found: (.+)", CaptureSource::Stdout),
102 )]);
103
104 let result = resolve_captures(&captures, "nothing here", "");
105 assert!(result.is_empty());
106 }
107
108 #[test]
109 fn test_resolve_captures_multiple() {
110 let captures = HashMap::from([
111 (
112 "version".to_string(),
113 make_capture(r"version: (.+)", CaptureSource::Stdout),
114 ),
115 (
116 "url".to_string(),
117 make_capture(r"URL: (.+)", CaptureSource::Stdout),
118 ),
119 ]);
120
121 let stdout = "version: 1.2.3\nURL: https://example.com";
122 let result = resolve_captures(&captures, stdout, "");
123 assert_eq!(result.len(), 2);
124 assert_eq!(result.get("version").map(String::as_str), Some("1.2.3"));
125 assert_eq!(
126 result.get("url").map(String::as_str),
127 Some("https://example.com")
128 );
129 }
130
131 #[test]
132 fn test_resolve_captures_empty_output() {
133 let captures = HashMap::from([(
134 "url".to_string(),
135 make_capture(r"URL: (.+)", CaptureSource::Stdout),
136 )]);
137
138 let result = resolve_captures(&captures, "", "");
139 assert!(result.is_empty());
140 }
141
142 #[test]
143 fn test_resolve_captures_invalid_regex() {
144 let captures = HashMap::from([(
145 "bad".to_string(),
146 make_capture(r"[invalid(", CaptureSource::Stdout),
147 )]);
148
149 let result = resolve_captures(&captures, "anything", "");
150 assert!(result.is_empty());
151 }
152
153 #[test]
154 fn test_resolve_captures_trims_whitespace() {
155 let captures = HashMap::from([(
156 "val".to_string(),
157 make_capture(r"value: (.+)", CaptureSource::Stdout),
158 )]);
159
160 let result = resolve_captures(&captures, "value: hello ", "");
161 assert_eq!(result.get("val").map(String::as_str), Some("hello"));
162 }
163}