cargo_e/
e_command_builder.rs

1use regex::Regex;
2use std::collections::{HashMap, HashSet};
3use std::env;
4use std::io::Read;
5use std::path::{Path, PathBuf};
6use std::process::Command;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::mpsc::{channel, Sender};
9use std::time::SystemTime;
10use which::which;
11
12use crate::e_cargocommand_ext::CargoProcessResult;
13use crate::e_cargocommand_ext::{CargoCommandExt, CargoDiagnostic, CargoProcessHandle};
14use crate::e_eventdispatcher::{
15    CallbackResponse, CallbackType, CargoDiagnosticLevel, EventDispatcher,
16};
17use crate::e_runner::GLOBAL_CHILDREN;
18use crate::e_target::{CargoTarget, TargetKind, TargetOrigin};
19use std::sync::{Arc, Mutex};
20
21#[derive(Debug, Clone, PartialEq, Copy)]
22pub enum TerminalError {
23    NotConnected,
24    NoTerminal,
25    NoError,
26}
27
28impl Default for TerminalError {
29    fn default() -> Self {
30        TerminalError::NoError
31    }
32}
33
34/// A builder that constructs a Cargo command for a given target.
35#[derive(Clone, Debug)]
36pub struct CargoCommandBuilder {
37    pub target_name: String,
38    pub manifest_path: PathBuf,
39    pub args: Vec<String>,
40    pub subcommand: String,
41    pub pid: Option<u32>,
42    pub alternate_cmd: Option<String>,
43    pub execution_dir: Option<PathBuf>,
44    pub suppressed_flags: HashSet<String>,
45    pub stdout_dispatcher: Option<Arc<EventDispatcher>>,
46    pub stderr_dispatcher: Option<Arc<EventDispatcher>>,
47    pub progress_dispatcher: Option<Arc<EventDispatcher>>,
48    pub stage_dispatcher: Option<Arc<EventDispatcher>>,
49    pub terminal_error_flag: Arc<Mutex<bool>>,
50    pub sender: Option<Arc<Mutex<Sender<TerminalError>>>>,
51    pub diagnostics: Arc<Mutex<Vec<CargoDiagnostic>>>,
52    pub is_filter: bool,
53}
54
55impl std::fmt::Display for CargoCommandBuilder {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        write!(
58            f,
59            "CargoCommandBuilder {{\n  target_name: {:?},\n  manifest_path: {:?},\n  args: {:?},\n  subcommand: {:?},\n  pid: {:?},\n  alternate_cmd: {:?},\n  execution_dir: {:?},\n  suppressed_flags: {:?},\n  is_filter: {:?}\n}}",
60            self.target_name,
61            self.manifest_path,
62            self.args,
63            self.subcommand,
64            self.pid,
65            self.alternate_cmd,
66            self.execution_dir,
67            self.suppressed_flags,
68            self.is_filter
69        )
70    }
71}
72impl Default for CargoCommandBuilder {
73    fn default() -> Self {
74        Self::new(
75            &String::new(),
76            &PathBuf::from("Cargo.toml"),
77            "run".into(),
78            false,
79        )
80    }
81}
82impl CargoCommandBuilder {
83    /// Creates a new, empty builder.
84    pub fn new(target_name: &str, manifest: &PathBuf, subcommand: &str, is_filter: bool) -> Self {
85        let (sender, _receiver) = channel::<TerminalError>();
86        let sender = Arc::new(Mutex::new(sender));
87        let mut builder = CargoCommandBuilder {
88            target_name: target_name.to_owned(),
89            manifest_path: manifest.clone(),
90            args: Vec::new(),
91            subcommand: subcommand.to_string(),
92            pid: None,
93            alternate_cmd: None,
94            execution_dir: None,
95            suppressed_flags: HashSet::new(),
96            stdout_dispatcher: None,
97            stderr_dispatcher: None,
98            progress_dispatcher: None,
99            stage_dispatcher: None,
100            terminal_error_flag: Arc::new(Mutex::new(false)),
101            sender: Some(sender),
102            diagnostics: Arc::new(Mutex::new(Vec::<CargoDiagnostic>::new())),
103            is_filter,
104        };
105        builder.set_default_dispatchers();
106
107        builder
108    }
109
110    // Switch to passthrough mode when the terminal error is detected
111    fn switch_to_passthrough_mode<F>(self: Arc<Self>, on_spawn: F) -> anyhow::Result<u32>
112    where
113        F: FnOnce(u32, CargoProcessHandle),
114    {
115        let mut command = self.build_command();
116
117        // Now, spawn the cargo process in passthrough mode
118        let cargo_process_handle = command.spawn_cargo_passthrough(Arc::clone(&self));
119        let pid = cargo_process_handle.pid;
120        // Notify observer
121        on_spawn(pid, cargo_process_handle);
122
123        Ok(pid)
124    }
125
126    // Set up the default dispatchers, which includes error detection
127    fn set_default_dispatchers(&mut self) {
128        if !self.is_filter {
129            // If this is a filter, we don't need to set up dispatchers
130            return;
131        }
132        let sender = self.sender.clone().unwrap();
133
134        let mut stdout_dispatcher = EventDispatcher::new();
135        stdout_dispatcher.add_callback(
136            r"listening on",
137            Box::new(|line, _captures, _state, stats| {
138                println!("(STDOUT) Dispatcher caught: {}", line);
139                // Use a regex to capture a URL from the line.
140                if let Ok(url_regex) = Regex::new(r"(http://[^\s]+)") {
141                    if let Some(url_caps) = url_regex.captures(line) {
142                        if let Some(url_match) = url_caps.get(1) {
143                            let url = url_match.as_str();
144                            // Call open::that on the captured URL.
145                            if let Err(e) = open::that_detached(url) {
146                                eprintln!("Failed to open URL: {}. Error: {}", url, e);
147                            } else {
148                                println!("Opened URL: {}", url);
149                            }
150                        }
151                    }
152                } else {
153                    eprintln!("Failed to create URL regex");
154                }
155                let mut stats = stats.lock().unwrap();
156                if stats.build_finished_time.is_none() {
157                    let now = SystemTime::now();
158                    stats.build_finished_time = Some(now);
159                }
160                None
161            }),
162        );
163
164        stdout_dispatcher.add_callback(
165            r"BuildFinished",
166            Box::new(|line, _captures, _state, stats| {
167                println!("******* {}", line);
168                let mut stats = stats.lock().unwrap();
169                if stats.build_finished_time.is_none() {
170                    let now = SystemTime::now();
171                    stats.build_finished_time = Some(now);
172                }
173                None
174            }),
175        );
176        stdout_dispatcher.add_callback(
177            r"server listening at:",
178            Box::new(|line, _captures, state, stats| {
179                // If we're not already in multiline mode, this is the initial match.
180                if !state.load(Ordering::Relaxed) {
181                    println!("Matched 'server listening at:' in: {}", line);
182                    state.store(true, Ordering::Relaxed);
183                    Some(CallbackResponse {
184                        callback_type: CallbackType::Note, // Choose as appropriate
185                        message: Some(format!("Started multiline mode after: {}", line)),
186                        file: None,
187                        line: None,
188                        column: None,
189                        suggestion: None,
190                        terminal_status: None,
191                    })
192                } else {
193                    // We are in multiline mode; process subsequent lines.
194                    println!("Multiline callback received: {}", line);
195                    // Use a regex to capture a URL from the line.
196                    let url_regex = match Regex::new(r"(http://[^\s]+)") {
197                        Ok(regex) => regex,
198                        Err(e) => {
199                            eprintln!("Failed to create URL regex: {}", e);
200                            return None;
201                        }
202                    };
203                    if let Some(url_caps) = url_regex.captures(line) {
204                        let url = url_caps.get(1).unwrap().as_str();
205                        // Call open::that on the captured URL.
206                        match open::that_detached(url) {
207                            Ok(_) => println!("Opened URL: {}", url),
208                            Err(e) => eprintln!("Failed to open URL: {}. Error: {}", url, e),
209                        }
210                        let mut stats = stats.lock().unwrap();
211                        if stats.build_finished_time.is_none() {
212                            let now = SystemTime::now();
213                            stats.build_finished_time = Some(now);
214                        }
215                        // End multiline mode.
216                        state.store(false, Ordering::Relaxed);
217                        Some(CallbackResponse {
218                            callback_type: CallbackType::Note, // Choose as appropriate
219                            message: Some(format!("Captured and opened URL: {}", url)),
220                            file: None,
221                            line: None,
222                            column: None,
223                            suggestion: None,
224                            terminal_status: None,
225                        })
226                    } else {
227                        None
228                    }
229                }
230            }),
231        );
232
233        let mut stderr_dispatcher = EventDispatcher::new();
234
235        let suggestion_mode = Arc::new(AtomicBool::new(false));
236        let suggestion_regex = Regex::new(r"^\s*(\d+)\s*\|\s*(.*)$").unwrap();
237        let warning_location: Arc<Mutex<Option<CallbackResponse>>> = Arc::new(Mutex::new(None));
238        let pending_diag: Arc<Mutex<Option<CargoDiagnostic>>> = Arc::new(Mutex::new(None));
239        let diagnostic_counts: Arc<Mutex<HashMap<CargoDiagnosticLevel, usize>>> =
240            Arc::new(Mutex::new(HashMap::new()));
241
242        let pending_d = Arc::clone(&pending_diag);
243        let counts = Arc::clone(&diagnostic_counts);
244
245        let diagnostics_arc = Arc::clone(&self.diagnostics);
246
247        // Callback for Rust panic messages (e.g., "thread 'main' panicked at ...")
248        stderr_dispatcher.add_callback(
249            r"^thread '([^']+)' panicked at (.+):([^\s:]+):(\d+):(\d+)",
250            Box::new(|line, captures, _state, _stats| {
251                if let Some(caps) = captures {
252                    let thread = caps.get(1).map(|m| m.as_str()).unwrap_or("unknown");
253                    let message = caps.get(2).map(|m| m.as_str()).unwrap_or("unknown panic");
254                    let file = caps.get(3).map(|m| m.as_str()).unwrap_or("unknown file");
255                    let line_num = caps
256                        .get(4)
257                        .map(|m| m.as_str())
258                        .unwrap_or("0")
259                        .parse()
260                        .unwrap_or(0);
261                    let col_num = caps
262                        .get(5)
263                        .map(|m| m.as_str())
264                        .unwrap_or("0")
265                        .parse()
266                        .unwrap_or(0);
267                    println!("\n\n\n");
268                    println!("{}", line);
269                    println!(
270                        "Panic detected: thread='{}', message='{}', file='{}:{}:{}'",
271                        thread, message, file, line_num, col_num
272                    );
273                    println!("\n\n\n");
274                    Some(CallbackResponse {
275                        callback_type: CallbackType::Error,
276                        message: Some(format!(
277                            "thread '{}' panicked at {} ({}:{}:{})",
278                            thread, message, file, line_num, col_num
279                        )),
280                        file: Some(file.to_string()),
281                        line: Some(line_num),
282                        column: Some(col_num),
283                        suggestion: None,
284                        terminal_status: None,
285                    })
286                } else {
287                    None
288                }
289            }),
290        );
291
292        // Add a callback to detect "could not compile" errors
293        stderr_dispatcher.add_callback(
294            r"error: could not compile `(?P<crate_name>.+)` \((?P<due_to>.+)\) due to (?P<error_count>\d+) previous errors; (?P<warning_count>\d+) warnings emitted",
295            Box::new(|line, captures, _state, stats| {
296                println!("{}", line);
297            if let Some(caps) = captures {
298                // Extract dynamic fields from the error message
299                let crate_name = caps.name("crate_name").map(|m| m.as_str()).unwrap_or("unknown");
300                let due_to = caps.name("due_to").map(|m| m.as_str()).unwrap_or("unknown");
301                let error_count: usize = caps
302                .name("error_count")
303                .map(|m| m.as_str().parse().unwrap_or(0))
304                .unwrap_or(0);
305                let warning_count: usize = caps
306                .name("warning_count")
307                .map(|m| m.as_str().parse().unwrap_or(0))
308                .unwrap_or(0);
309
310                // Log the captured information (optional)
311                println!(
312                "Detected compilation failure: crate=`{}`, due_to=`{}`, errors={}, warnings={}",
313                crate_name, due_to, error_count, warning_count
314                );
315
316                // Set `is_could_not_compile` to true in the stats
317                let mut stats = stats.lock().unwrap();
318                stats.is_could_not_compile = true;
319            }
320            None // No callback response needed
321            }),
322        );
323
324        // Clone diagnostics_arc for this closure to avoid move
325        let diagnostics_arc_for_diag = Arc::clone(&diagnostics_arc);
326        stderr_dispatcher.add_callback(
327            r"^(?P<level>\w+)(\[(?P<error_code>E\d+)\])?:\s+(?P<msg>.+)$", // Regex for diagnostic line
328            Box::new(move |_line, caps, _multiline_flag, _stats| {
329                if let Some(caps) = caps {
330                    let mut counts = counts.lock().unwrap();
331                    // Create a PendingDiag and save the message
332                    let mut pending_diag = pending_d.lock().unwrap();
333                    let mut last_lineref = String::new();
334                    if let Some(existing_diag) = pending_diag.take() {
335                        let mut diags = diagnostics_arc_for_diag.lock().unwrap();
336                        last_lineref = existing_diag.lineref.clone();
337                        diags.push(existing_diag.clone());
338                    }
339                    log::trace!("Diagnostic line: {}", _line);
340                    let level = caps["level"].to_string(); // e.g., "warning", "error"
341                    let message = caps["msg"].to_string();
342                    // If the message contains "generated" followed by one or more digits,
343                    // then ignore this diagnostic by returning None.
344                    let re_generated = regex::Regex::new(r"generated\s+\d+").unwrap();
345                    if re_generated.is_match(&message) {
346                        log::trace!("Skipping generated diagnostic: {}", _line);
347                        return None;
348                    }
349
350                    let error_code = caps.name("error_code").map(|m| m.as_str().to_string());
351                    let diag_level = match level.as_str() {
352                        "error" => CargoDiagnosticLevel::Error,
353                        "warning" => CargoDiagnosticLevel::Warning,
354                        "help" => CargoDiagnosticLevel::Help,
355                        "note" => CargoDiagnosticLevel::Note,
356                        _ => {
357                            println!("Unknown diagnostic level: {}", level);
358                            return None; // Ignore unknown levels
359                        }
360                    };
361                    // Increment the count for this level
362                    *counts.entry(diag_level).or_insert(0) += 1;
363
364                    let current_count = counts.get(&diag_level).unwrap_or(&0);
365                    let diag = CargoDiagnostic {
366                        error_code: error_code.clone(),
367                        lineref: last_lineref.clone(),
368                        level: level.clone(),
369                        message,
370                        suggestion: None,
371                        help: None,
372                        note: None,
373                        uses_color: true,
374                        diag_num_padding: Some(2),
375                        diag_number: Some(*current_count),
376                    };
377
378                    // Save the new diagnostic
379                    *pending_diag = Some(diag);
380
381                    // Track the count of diagnostics for each level
382                    return Some(CallbackResponse {
383                        callback_type: CallbackType::LevelMessage, // Treat subsequent lines as warnings
384                        message: None,
385                        file: None,
386                        line: None,
387                        column: None,
388                        suggestion: None, // This is the suggestion part
389                        terminal_status: None,
390                    });
391                } else {
392                    println!("No captures found in line: {}", _line);
393                    None
394                }
395            }),
396        );
397        // Look-behind buffer for last 6 lines before backtrace
398        let look_behind = Arc::new(Mutex::new(Vec::<String>::new()));
399        {
400            let look_behind = Arc::clone(&look_behind);
401            // This callback runs for every stderr line to update the look-behind buffer
402            stderr_dispatcher.add_callback(
403                r"^(?P<msg>.*)$",
404                Box::new(move |line, _captures, _state, _stats| {
405                    let mut buf = look_behind.lock().unwrap();
406                    if line.trim().is_empty() {
407                        return None;
408                    }
409                    buf.push(line.to_string());
410                    if buf.len() > 6 {
411                        buf.remove(0);
412                    }
413                    None
414                }),
415            );
416        }
417
418        // --- Patch: Use look_behind before backtrace_lines in the note ---
419        {
420            let pending_diag = Arc::clone(&pending_diag);
421            let diagnostics_arc = Arc::clone(&diagnostics_arc);
422            let backtrace_mode = Arc::new(AtomicBool::new(false));
423            let backtrace_lines = Arc::new(Mutex::new(Vec::<String>::new()));
424            let look_behind = Arc::clone(&look_behind);
425            let stored_lines_behind = Arc::new(Mutex::new(Vec::<String>::new()));
426
427            // Enable backtrace mode when we see "stack backtrace:"
428            {
429                let backtrace_mode = Arc::clone(&backtrace_mode);
430                let backtrace_lines = Arc::clone(&backtrace_lines);
431                let stored_lines_behind = Arc::clone(&stored_lines_behind);
432                let look_behind = Arc::clone(&look_behind);
433                stderr_dispatcher.add_callback(
434                    r"stack backtrace:",
435                    Box::new(move |_line, _captures, _state, _stats| {
436                        backtrace_mode.store(true, Ordering::Relaxed);
437                        backtrace_lines.lock().unwrap().clear();
438                        // Save the current look_behind buffer into a shared stored_lines_behind for later use
439                        {
440                            let look_behind_buf = look_behind.lock().unwrap();
441                            let mut stored = stored_lines_behind.lock().unwrap();
442                            *stored = look_behind_buf.clone();
443                        }
444                        None
445                    }),
446                );
447            }
448
449            // Process backtrace lines, filter and summarize
450            {
451                let backtrace_mode = Arc::clone(&backtrace_mode);
452                let backtrace_lines = Arc::clone(&backtrace_lines);
453                let pending_diag = Arc::clone(&pending_diag);
454                let diagnostics_arc = Arc::clone(&diagnostics_arc);
455                let look_behind = Arc::clone(&look_behind);
456
457                // Regex for numbered backtrace line: "  0: type::path"
458                let re_number_type = Regex::new(r"^\s*(\d+):\s+(.*)$").unwrap();
459                // Regex for "at path:line"
460                let re_at_path = Regex::new(r"^\s*at\s+([^\s:]+):(\d+)").unwrap();
461
462                stderr_dispatcher.add_callback(
463                    r"^(?P<msg>.*)$",
464                    Box::new(move |mut line, _captures, _state, _stats| {
465                        if backtrace_mode.load(Ordering::Relaxed) {
466                            line = line.trim();
467                            // End of backtrace if empty line or new diagnostic/note
468                            if line.trim().is_empty()
469                                || line.starts_with("note:")
470                                || line.starts_with("error:")
471                            {
472                                let mut bt_lines = Vec::new();
473                                let mut skip_next = false;
474                                let mut last_number_type: Option<(String, String)> = None;
475                                for l in backtrace_lines.lock().unwrap().iter() {
476                                    if let Some(caps) = re_number_type.captures(l) {
477                                        // Save the (number, type) line, but don't push yet
478                                        last_number_type =
479                                            Some((caps[1].to_string(), caps[2].to_string()));
480                                        skip_next = true;
481                                    } else if skip_next && re_at_path.is_match(l) {
482                                        let path_caps = re_at_path.captures(l).unwrap();
483                                        let path = path_caps.get(1).unwrap().as_str();
484                                        let line_num = path_caps.get(2).unwrap().as_str();
485                                        if path.starts_with("/rustc")
486                                            || path.contains(".cargo")
487                                            || path.contains(".rustup")
488                                        {
489                                            // Skip both the number: type and the at line
490                                            // (do not push either)
491                                        } else {
492                                            // Push both the number: type and the at line, on the same line
493                                            if let Some((num, typ)) = last_number_type.take() {
494                                                // Canonicalize the path if possible for better readability
495                                                let path = match std::fs::canonicalize(path) {
496                                                    Ok(canon) => canon.display().to_string(),
497                                                    Err(_) => path.to_string(),
498                                                };
499
500                                                bt_lines.push(format!(
501                                                    "{}: {} @ {}:{}",
502                                                    num, typ, path, line_num
503                                                ));
504                                            }
505                                        }
506                                        skip_next = false;
507                                    } else if let Some((num, typ)) = last_number_type.take() {
508                                        // If the previous number: type was not followed by an at line, push it
509                                        bt_lines.push(format!("{}: {}", num, typ));
510                                        if !l.trim().is_empty() {
511                                            bt_lines.push(l.clone());
512                                        }
513                                        skip_next = false;
514                                    } else if !l.trim().is_empty() {
515                                        bt_lines.push(l.clone());
516                                        skip_next = false;
517                                    }
518                                }
519                                if !bt_lines.is_empty() {
520                                    let mut pending_diag = pending_diag.lock().unwrap();
521                                    if let Some(ref mut diag) = *pending_diag {
522                                        // --- Insert stored_lines_behind lines before backtrace_lines ---
523                                        let stored_lines = {
524                                            let buf = stored_lines_behind.lock().unwrap();
525                                            buf.clone()
526                                        };
527                                        let note = diag.note.get_or_insert_with(String::new);
528                                        if !stored_lines.is_empty() {
529                                            note.push_str(&stored_lines.join("\n"));
530                                            note.push('\n');
531                                        }
532                                        note.push_str(&bt_lines.join("\n"));
533                                        let mut diags = diagnostics_arc.lock().unwrap();
534                                        diags.push(diag.clone());
535                                    }
536                                }
537                                backtrace_mode.store(false, Ordering::Relaxed);
538                                backtrace_lines.lock().unwrap().clear();
539                                return None;
540                            }
541
542                            // Only keep lines that are part of the backtrace
543                            if re_number_type.is_match(line) || re_at_path.is_match(line) {
544                                backtrace_lines.lock().unwrap().push(line.to_string());
545                            }
546                            // Ignore further lines
547                            return None;
548                        }
549                        None
550                    }),
551                );
552            }
553        }
554
555        // suggestion callback
556        {
557            let location_lock_clone = Arc::clone(&warning_location);
558            let suggestion_m = Arc::clone(&suggestion_mode);
559
560            // Suggestion callback that adds subsequent lines as suggestions
561            stderr_dispatcher.add_callback(
562                r"^(?P<msg>.*)$", // Capture all lines following the location
563                Box::new(move |line, _captures, _multiline_flag, _stats| {
564                    if suggestion_m.load(Ordering::Relaxed) {
565                        // Only process lines that match the suggestion format
566                        if let Some(caps) = suggestion_regex.captures(line.trim()) {
567                            // Capture the line number and code from the suggestion line
568                            // let line_num = caps[1].parse::<usize>().unwrap_or(0);
569                            let code = caps[2].to_string();
570
571                            // Lock the pending_diag to add the suggestion
572                            if let Ok(mut lock) = location_lock_clone.lock() {
573                                if let Some(mut loc) = lock.take() {
574                                    // let file = loc.file.clone().unwrap_or_default();
575                                    // let col = loc.column.unwrap_or(0);
576
577                                    // Concatenate the suggestion line to the message
578                                    let mut msg = loc.message.unwrap_or_default();
579                                    msg.push_str(&format!("\n{}", code));
580
581                                    // Print the concatenated suggestion for debugging
582                                    // println!("daveSuggestion for {}:{}:{} - {}", file, line_num, col, msg);
583
584                                    // Update the location with the new concatenated message
585                                    loc.message = Some(msg.clone());
586                                    // println!("Updating location lock with new suggestion: {}", msg);
587                                    // Save the updated location back to shared state
588                                    // if let Ok(mut lock) = location_lock_clone.lock() {
589                                    // println!("Updating location lock with new suggestion: {}", msg);
590                                    lock.replace(loc);
591                                    // } else {
592                                    //     eprintln!("Failed to acquire lock for location_lock_clone");
593                                    // }
594                                }
595                                // return Some(CallbackResponse {
596                                //     callback_type: CallbackType::Warning, // Treat subsequent lines as warnings
597                                //     message: Some(msg.clone()),
598                                //     file: Some(file),
599                                //     line: Some(line_num),
600                                //     column: Some(col),
601                                //     suggestion: Some(msg),  // This is the suggestion part
602                                //     terminal_status: None,
603                                // });
604                            }
605                        }
606                    } else {
607                        // println!("Suggestion mode is not active. Ignoring line: {}", line);
608                    }
609
610                    None
611                }),
612            );
613        }
614        {
615            let suggestion_m = Arc::clone(&suggestion_mode);
616            let pending_diag_clone = Arc::clone(&pending_diag);
617            let diagnostics_arc = Arc::clone(&self.diagnostics);
618            // Callback for handling when an empty line or new diagnostic is received
619            stderr_dispatcher.add_callback(
620                r"^\s*$", // Regex to capture empty line
621                Box::new(move |_line, _captures, _multiline_flag, _stats| {
622                    // println!("Empty line detected: {}", line);
623                    suggestion_m.store(false, Ordering::Relaxed);
624                    // End of current diagnostic: take and process it.
625                    if let Some(pending_diag) = pending_diag_clone.lock().unwrap().take() {
626                        //println!("{:?}", pending_diag);
627                        // Use diagnostics_arc instead of self.diagnostices
628                        let mut diags = diagnostics_arc.lock().unwrap();
629                        diags.push(pending_diag.clone());
630                    } else {
631                        // println!("No pending diagnostic to process.");
632                    }
633                    // Handle empty line scenario to end the current diagnostic processing
634                    // if let Some(pending_diag) = pending_diag_clone.lock().unwrap().take() {
635                    //     println!("{:?}", pending_diag);
636                    //     let mut diags = self.diagnostics.lock().unwrap();
637                    //     diags.push(pending_diag.clone());
638                    //                             // let diag = crate::e_eventdispatcher::convert_message_to_diagnostic(msg, &msg_str);
639                    //                             // diags.push(diag.clone());
640                    //                             // if let Some(ref sd) = stage_disp_clone {
641                    //                             //     sd.dispatch(&format!("Stage: Diagnostic occurred at {:?}", now));
642                    //                             // }
643                    //     // Handle the saved PendingDiag and its CallbackResponse
644                    //     // if let Some(callback_response) = pending_diag.callback_response {
645                    //     //     println!("End of Diagnostic: {:?}", callback_response);
646                    //     // }
647                    // } else {
648                    //     println!("No pending diagnostic to process.");
649                    // }
650
651                    None
652                }),
653            );
654        }
655
656        // {
657        //     let pending_diag = Arc::clone(&pending_diag);
658        //     let location_lock = Arc::clone(&warning_location);
659        //     let suggestion_m = Arc::clone(&suggestion_mode);
660
661        // let suggestion_regex = Regex::new(r"^\s*(\d+)\s*\|\s*(.*)$").unwrap();
662
663        //     stderr_dispatcher.add_callback(
664        //     r"^\s*(\d+)\s*\|\s*(.*)$",  // Match suggestion line format
665        //     Box::new(move |line, _captures, _multiline_flag| {
666        //         if suggestion_m.load(Ordering::Relaxed) {
667        //             // Only process lines that match the suggestion format
668        //             if let Some(caps) = suggestion_regex.captures(line.trim()) {
669        //                 // Capture the line number and code from the suggestion line
670        //                 let line_num = caps[1].parse::<usize>().unwrap_or(0);
671        //                 let code = caps[2].to_string();
672
673        //                 // Lock the pending_diag to add the suggestion
674        //                 if let Some(mut loc) = location_lock.lock().unwrap().take() {
675        //                     println!("Suggestion line: {}", line);
676        //                     let file = loc.file.clone().unwrap_or_default();
677        //                     let col = loc.column.unwrap_or(0);
678
679        //                     // Concatenate the suggestion line to the message
680        //                     let mut msg = loc.message.unwrap_or_default();
681        //                     msg.push_str(&format!("\n{} | {}", line_num, code));  // Append the suggestion properly
682
683        //                     // Print the concatenated suggestion for debugging
684        //                     println!("Suggestion for {}:{}:{} - {}", file, line_num, col, msg);
685
686        //                     // Update the location with the new concatenated message
687        //                     loc.message = Some(msg.clone());
688
689        //                     // Save the updated location back to shared state
690        //                     location_lock.lock().unwrap().replace(loc);
691
692        //                     // return Some(CallbackResponse {
693        //                     //     callback_type: CallbackType::Warning, // Treat subsequent lines as warnings
694        //                     //     message: Some(msg.clone()),
695        //                     //     file: Some(file),
696        //                     //     line: Some(line_num),
697        //                     //     column: Some(col),
698        //                     //     suggestion: Some(msg),  // This is the suggestion part
699        //                     //     terminal_status: None,
700        //                     // });
701        //                 } else {
702        //                     println!("No location information available for suggestion line: {}", line);
703        //                 }
704        //             } else {
705        //                 println!("Suggestion line does not match expected format: {}", line);
706        //             }
707        //         } else {
708        //             println!("Suggestion mode is not active. Ignoring line: {}", line);
709        //         }
710
711        //         None
712        //     }),
713        // );
714
715        // }
716
717        {
718            let location_lock = Arc::clone(&warning_location);
719            let pending_diag = Arc::clone(&pending_diag);
720            let suggestion_mode = Arc::clone(&suggestion_mode);
721            stderr_dispatcher.add_callback(
722                r"^(?P<msg>.*)$", // Capture all lines following the location
723                Box::new(move |line, _captures, _multiline_flag, _stats| {
724                    // Lock the location to fetch the original diagnostic info
725                    if let Ok(location_guard) = location_lock.lock() {
726                        if let Some(loc) = location_guard.as_ref() {
727                            let file = loc.file.clone().unwrap_or_default();
728                            let line_num = loc.line.unwrap_or(0);
729                            let col = loc.column.unwrap_or(0);
730                            // println!("SUGGESTION: Suggestion for {}:{}:{} {}", file, line_num, col, line);
731
732                            // Only treat lines starting with | or numbers as suggestion lines
733                            if line.trim().starts_with('|')
734                                || line.trim().starts_with(char::is_numeric)
735                            {
736                                // Get the existing suggestion and append the new line
737                                let suggestion = line.trim();
738
739                                // Print the suggestion for debugging
740                                // println!("Suggestion for {}:{}:{} - {}", file, line_num, col, suggestion);
741
742                                // Lock the pending_diag and update its callback_response field
743                                let mut pending_diag = match pending_diag.lock() {
744                                    Ok(lock) => lock,
745                                    Err(e) => {
746                                        eprintln!("Failed to acquire lock: {}", e);
747                                        return None; // Handle the error appropriately
748                                    }
749                                };
750                                if let Some(diag) = pending_diag.take() {
751                                    // If a PendingDiag already exists, update the existing callback response with the new suggestion
752                                    let mut diag = diag;
753
754                                    // Append the new suggestion to the existing one
755                                    if let Some(ref mut existing) = diag.suggestion {
756                                        diag.suggestion =
757                                            Some(format!("{}\n{}", existing, suggestion));
758                                    } else {
759                                        diag.suggestion = Some(suggestion.to_string());
760                                    }
761
762                                    // Update the shared state with the new PendingDiag
763                                    *pending_diag = Some(diag.clone());
764                                    return Some(CallbackResponse {
765                                        callback_type: CallbackType::Suggestion, // Treat subsequent lines as warnings
766                                        message: Some(
767                                            diag.clone().suggestion.clone().unwrap().clone(),
768                                        ),
769                                        file: Some(file),
770                                        line: Some(line_num),
771                                        column: Some(col),
772                                        suggestion: diag.clone().suggestion.clone(), // This is the suggestion part
773                                        terminal_status: None,
774                                    });
775                                } else {
776                                    // println!("No pending diagnostic to process for suggestion line: {}", line);
777                                }
778                            } else {
779                                // If the line doesn't match the suggestion format, just return it as is
780                                if line.trim().is_empty() {
781                                    // Ignore empty lines
782                                    suggestion_mode.store(false, Ordering::Relaxed);
783                                    return None;
784                                }
785                            }
786                        } else {
787                            // println!("No location information available for suggestion line: {}", line);
788                        }
789                    }
790                    None
791                }),
792            );
793        }
794
795        // 2) Location callback stores its response into that shared state
796        {
797            let pending_diag = Arc::clone(&pending_diag);
798            let warning_location = Arc::clone(&warning_location);
799            let location_lock = Arc::clone(&warning_location);
800            let suggestion_mode = Arc::clone(&suggestion_mode);
801            let manifest_path = self.manifest_path.clone();
802            stderr_dispatcher.add_callback(
803                // r"^\s*-->\s+(?P<file>[^:]+):(?P<line>\d+):(?P<col>\d+)$",
804                r"^\s*-->\s+(?P<file>.+?)(?::(?P<line>\d+))?(?::(?P<col>\d+))?\s*$",
805                Box::new(move |_line, caps, _multiline_flag, _stats| {
806                    log::trace!("Location line: {}", _line);
807                    // if multiline_flag.load(Ordering::Relaxed) {
808                    if let Some(caps) = caps {
809                        let file = caps["file"].to_string();
810                        let resolved_path = resolve_file_path(&manifest_path, &file);
811                        let file = resolved_path.to_str().unwrap_or_default().to_string();
812                        let line = caps["line"].parse::<usize>().unwrap_or(0);
813                        let column = caps["col"].parse::<usize>().unwrap_or(0);
814                        let resp = CallbackResponse {
815                            callback_type: CallbackType::Location,
816                            message: format!("{}:{}:{}", file, line, column).into(),
817                            file: Some(file.clone()),
818                            line: Some(line),
819                            column: Some(column),
820                            suggestion: None,
821                            terminal_status: None,
822                        };
823                        // Lock the pending_diag and update its callback_response field
824                        let mut pending_diag = pending_diag.lock().unwrap();
825                        if let Some(diag) = pending_diag.take() {
826                            // If a PendingDiag already exists, save the new callback response in the existing PendingDiag
827                            let mut diag = diag;
828                            diag.lineref = format!("{}:{}:{}", file, line, column); // Update the lineref
829                                                                                    // diag.save_callback_response(resp.clone()); // Save the callback response
830                                                                                    // Update the shared state with the new PendingDiag
831                            *pending_diag = Some(diag);
832                        }
833                        // Save it for the generic callback to see
834                        *warning_location.lock().unwrap() = Some(resp.clone());
835                        *location_lock.lock().unwrap() = Some(resp.clone());
836                        // Set suggestion mode to true as we've encountered a location line
837                        suggestion_mode.store(true, Ordering::Relaxed);
838                        return Some(resp.clone());
839                    } else {
840                        println!("No captures found in line: {}", _line);
841                    }
842                    // }
843                    None
844                }),
845            );
846        }
847
848        // // 3) Note callback — attach note to pending_diag
849        {
850            let pending_diag = Arc::clone(&pending_diag);
851            stderr_dispatcher.add_callback(
852                r"^\s*=\s*note:\s*(?P<msg>.+)$",
853                Box::new(move |_line, caps, _state, _stats| {
854                    if let Some(caps) = caps {
855                        let mut pending_diag = pending_diag.lock().unwrap();
856                        if let Some(ref mut resp) = *pending_diag {
857                            // Prepare the new note with the blue prefix
858                            let new_note = format!("note: {}", caps["msg"].to_string());
859
860                            // Append or set the note
861                            if let Some(existing_note) = &resp.note {
862                                // If there's already a note, append with newline and the new note
863                                resp.note = Some(format!("{}\n{}", existing_note, new_note));
864                            } else {
865                                // If no existing note, just set the new note
866                                resp.note = Some(new_note);
867                            }
868                        }
869                    }
870                    None
871                }),
872            );
873        }
874
875        // 4) Help callback — attach help to pending_diag
876        {
877            let pending_diag = Arc::clone(&pending_diag);
878            stderr_dispatcher.add_callback(
879                r"^\s*(?:\=|\|)\s*help:\s*(?P<msg>.+)$", // Regex to match both '=' and '|' before help:
880                Box::new(move |_line, caps, _state, _stats| {
881                    if let Some(caps) = caps {
882                        let mut pending_diag = pending_diag.lock().unwrap();
883                        if let Some(ref mut resp) = *pending_diag {
884                            // Create the new help message with the orange "h:" prefix
885                            let new_help =
886                                format!("\x1b[38;5;214mhelp: {}\x1b[0m", caps["msg"].to_string());
887
888                            // Append or set the help message
889                            if let Some(existing_help) = &resp.help {
890                                // If there's already a help message, append with newline
891                                resp.help = Some(format!("{}\n{}", existing_help, new_help));
892                            } else {
893                                // If no existing help message, just set the new one
894                                resp.help = Some(new_help);
895                            }
896                        }
897                    }
898                    None
899                }),
900            );
901        }
902
903        stderr_dispatcher.add_callback(
904    r"(?:\x1b\[[0-9;]*[A-Za-z])*\s*Serving(?:\x1b\[[0-9;]*[A-Za-z])*\s+at\s+(http://[^\s]+)",
905    Box::new(|line, captures, _state, stats | {
906        if let Some(caps) = captures {
907            let url = caps.get(1).unwrap().as_str();
908            let url = url.replace("0.0.0.0", "127.0.0.1");
909            println!("(STDERR) Captured URL: {}", url);
910            match open::that_detached(&url) {
911                Ok(_) => println!("(STDERR) Opened URL: {}",&url),
912                Err(e) => eprintln!("(STDERR) Failed to open URL: {}. Error: {:?}", url, e),
913            }
914             let mut stats = stats.lock().unwrap();
915             if stats.build_finished_time.is_none() {
916              let now = SystemTime::now();
917             stats.build_finished_time = Some(now);
918             }
919            Some(CallbackResponse {
920                callback_type: CallbackType::OpenedUrl, // Choose as appropriate
921                message: Some(format!("Captured and opened URL: {}", url)),
922                file: None,
923                line: None,
924                column: None,
925                suggestion: None,
926                terminal_status: None,
927            })
928        } else {
929            println!("(STDERR) No URL captured in line: {}", line);
930            None
931        }
932    }),
933);
934
935        let finished_flag = Arc::new(AtomicBool::new(false));
936
937        // 0) Finished‐profile summary callback
938        {
939            let finished_flag = Arc::clone(&finished_flag);
940            stderr_dispatcher.add_callback(
941        r"^Finished\s+`(?P<profile>[^`]+)`\s+profile\s+\[(?P<opts>[^\]]+)\]\s+target\(s\)\s+in\s+(?P<dur>[0-9.]+s)$",
942        Box::new(move |_line, caps, _multiline_flag, stats | {
943            if let Some(caps) = caps {
944                finished_flag.store(true, Ordering::Relaxed);
945                let profile = &caps["profile"];
946                let opts    = &caps["opts"];
947                let dur     = &caps["dur"];
948                             let mut stats = stats.lock().unwrap();
949             if stats.build_finished_time.is_none() {
950              let now = SystemTime::now();
951             stats.build_finished_time = Some(now);
952             }
953                Some(CallbackResponse {
954                    callback_type: CallbackType::Note,
955                    message: Some(format!("Finished `{}` [{}] in {}", profile, opts, dur)),
956                    file: None, line: None, column: None, suggestion: None, terminal_status: None,
957                })
958            } else {
959                None
960            }
961        }),
962    );
963        }
964
965        let summary_flag = Arc::new(AtomicBool::new(false));
966        {
967            let summary_flag = Arc::clone(&summary_flag);
968            stderr_dispatcher.add_callback(
969    r"^(?P<level>warning|error):\s+`(?P<name>[^`]+)`\s+\((?P<otype>lib|bin)\)\s+generated\s+(?P<count>\d+)\s+(?P<kind>warnings|errors).*run\s+`(?P<cmd>[^`]+)`\s+to apply\s+(?P<fixes>\d+)\s+suggestions",
970    Box::new(move |_line, caps, multiline_flag, _stats | {
971        let summary_flag = Arc::clone(&summary_flag);
972        if let Some(caps) = caps {
973            summary_flag.store(true, Ordering::Relaxed);
974            // Always start fresh
975            multiline_flag.store(false, Ordering::Relaxed);
976
977            let level    = &caps["level"];
978            let name     = &caps["name"];
979            let otype    = &caps["otype"];
980            let count: usize = caps["count"].parse().unwrap_or(0);
981            let kind     = &caps["kind"];   // "warnings" or "errors"
982            let cmd      = caps["cmd"].to_string();
983            let fixes: usize = caps["fixes"].parse().unwrap_or(0);
984
985            println!("SUMMARIZATION CALLBACK {}",
986                    &format!("{}: `{}` ({}) generated {} {}; run `{}` to apply {} fixes",
987                    level, name, otype, count, kind, cmd, fixes));
988            Some(CallbackResponse {
989                callback_type: CallbackType::Note,  // treat as informational
990                message: Some(format!(
991                    "{}: `{}` ({}) generated {} {}; run `{}` to apply {} fixes",
992                    level, name, otype, count, kind, cmd, fixes
993                )),
994                file: None,
995                line: None,
996                column: None,
997                suggestion: Some(cmd),
998                terminal_status: None,
999            })
1000        } else {
1001            None
1002        }
1003    }),
1004    );
1005        }
1006
1007        // {
1008        //     let summary_flag = Arc::clone(&summary_flag);
1009        //     let finished_flag = Arc::clone(&finished_flag);
1010        //     let warning_location = Arc::clone(&warning_location);
1011        //     // Warning callback for stdout.
1012        //     stderr_dispatcher.add_callback(
1013        //         r"^warning:\s+(?P<msg>.+)$",
1014        //         Box::new(
1015        //             move |line: &str, captures: Option<regex::Captures>, multiline_flag: Arc<AtomicBool>| {
1016        //                             // If summary or finished just matched, skip
1017        //             if summary_flag.swap(false, Ordering::Relaxed)
1018        //                 || finished_flag.swap(false, Ordering::Relaxed)
1019        //             {
1020        //                 return None;
1021        //             }
1022
1023        //         // 2) If this line *matches* the warning regex, handle as a new warning
1024        //         if let Some(caps) = captures {
1025        //             let msg = caps.name("msg").unwrap().as_str().to_string();
1026        //                    // 1) If a location was saved, print file:line:col – msg
1027        //             // println!("*WARNING detected: {:?}", msg);
1028        //                 multiline_flag.store(true, Ordering::Relaxed);
1029        //         if let Some(loc) = warning_location.lock().unwrap().take() {
1030        //                 let file = loc.file.unwrap_or_default();
1031        //                 let line_num = loc.line.unwrap_or(0);
1032        //                 let col  = loc.column.unwrap_or(0);
1033        //                 println!("{}:{}:{} - {}", file, line_num, col, msg);
1034        //                 return Some(CallbackResponse {
1035        //                     callback_type: CallbackType::Warning,
1036        //                     message: Some(msg.to_string()),
1037        //                     file: None, line: None, column: None, suggestion: None, terminal_status: None,
1038        //                 });
1039        //         }
1040        //             return Some(CallbackResponse {
1041        //                 callback_type: CallbackType::Warning,
1042        //                 message: Some(msg),
1043        //                 file: None,
1044        //                 line: None,
1045        //                 column: None,
1046        //                 suggestion: None,
1047        //                 terminal_status: None,
1048        //             });
1049        //         }
1050
1051        //                 // 3) Otherwise, if we’re in multiline mode, treat as continuation
1052        //         if multiline_flag.load(Ordering::Relaxed) {
1053        //             let text = line.trim();
1054        //             if text.is_empty() {
1055        //                 multiline_flag.store(false, Ordering::Relaxed);
1056        //                 return None;
1057        //             }
1058        //             // println!("   - {:?}", text);
1059        //             return Some(CallbackResponse {
1060        //                 callback_type: CallbackType::Warning,
1061        //                 message: Some(text.to_string()),
1062        //                 file: None,
1063        //                 line: None,
1064        //                 column: None,
1065        //                 suggestion: None,
1066        //                 terminal_status: None,
1067        //             });
1068        //         }
1069        //                     None
1070        //             },
1071        //         ),
1072        //     );
1073        // }
1074
1075        stderr_dispatcher.add_callback(
1076            r"IO\(Custom \{ kind: NotConnected",
1077            Box::new(move |line, _captures, _state, _stats| {
1078                println!("(STDERR) Terminal error detected: {:?}", &line);
1079                let result = if line.contains("NotConnected") {
1080                    TerminalError::NoTerminal
1081                } else {
1082                    TerminalError::NoError
1083                };
1084                let sender = sender.lock().unwrap();
1085                sender.send(result).ok();
1086                Some(CallbackResponse {
1087                    callback_type: CallbackType::Warning, // Choose as appropriate
1088                    message: Some(format!("Terminal Error: {}", line)),
1089                    file: None,
1090                    line: None,
1091                    column: None,
1092                    suggestion: None,
1093                    terminal_status: None,
1094                })
1095            }),
1096        );
1097        stderr_dispatcher.add_callback(
1098            r".*",
1099            Box::new(|line, _captures, _state, _stats| {
1100                log::trace!("stdraw[{:?}]", line);
1101                None // We're just printing, so no callback response is needed.
1102            }),
1103        );
1104        // need to implement autosense/filtering for tool installers; TBD
1105        // stderr_dispatcher.add_callback(
1106        //     r"Command 'perl' not found\. Is perl installed\?",
1107        //     Box::new(|line, _captures, _state, stats| {
1108        //     println!("cargo e sees a perl issue; maybe a prompt in the future or auto-resolution.");
1109        //     crate::e_autosense::auto_sense_perl();
1110        //     None
1111        //     }),
1112        // );
1113        // need to implement autosense/filtering for tool installers; TBD
1114        // stderr_dispatcher.add_callback(
1115        //     r"Error configuring OpenSSL build:\s+Command 'perl' not found\. Is perl installed\?",
1116        //     Box::new(|line, _captures, _state, stats| {
1117        //     println!("Detected OpenSSL build error due to missing 'perl'. Attempting auto-resolution.");
1118        //     crate::e_autosense::auto_sense_perl();
1119        //     None
1120        //     }),
1121        // );
1122        self.stderr_dispatcher = Some(Arc::new(stderr_dispatcher));
1123
1124        // let mut progress_dispatcher = EventDispatcher::new();
1125        // progress_dispatcher.add_callback(r"Progress", Box::new(|line, _captures,_state| {
1126        //     println!("(Progress) {}", line);
1127        //     None
1128        // }));
1129        // self.progress_dispatcher = Some(Arc::new(progress_dispatcher));
1130
1131        // let mut stage_dispatcher = EventDispatcher::new();
1132        // stage_dispatcher.add_callback(r"Stage:", Box::new(|line, _captures, _state| {
1133        //     println!("(Stage) {}", line);
1134        //     None
1135        // }));
1136        // self.stage_dispatcher = Some(Arc::new(stage_dispatcher));
1137    }
1138
1139    pub fn run<F>(self: Arc<Self>, on_spawn: F) -> anyhow::Result<u32>
1140    where
1141        F: FnOnce(u32, CargoProcessHandle),
1142    {
1143        if !self.is_filter {
1144            return self.switch_to_passthrough_mode(on_spawn);
1145        }
1146        let mut command = self.build_command();
1147
1148        let mut cargo_process_handle = command.spawn_cargo_capture(
1149            self.clone(),
1150            self.stdout_dispatcher.clone(),
1151            self.stderr_dispatcher.clone(),
1152            self.progress_dispatcher.clone(),
1153            self.stage_dispatcher.clone(),
1154            None,
1155        );
1156        cargo_process_handle.diagnostics = Arc::clone(&self.diagnostics);
1157        let pid = cargo_process_handle.pid;
1158
1159        // Notify observer
1160        on_spawn(pid, cargo_process_handle);
1161
1162        Ok(pid)
1163    }
1164
1165    // pub fn run(self: Arc<Self>) -> anyhow::Result<u32> {
1166    //     // Build the command using the builder's configuration
1167    //     let mut command = self.build_command();
1168
1169    //     // Spawn the cargo process handle
1170    //     let cargo_process_handle = command.spawn_cargo_capture(
1171    //         self.stdout_dispatcher.clone(),
1172    //         self.stderr_dispatcher.clone(),
1173    //         self.progress_dispatcher.clone(),
1174    //         self.stage_dispatcher.clone(),
1175    //         None,
1176    //     );
1177    // let pid = cargo_process_handle.pid;
1178    // let mut global = GLOBAL_CHILDREN.lock().unwrap();
1179    // global.insert(pid, Arc::new(Mutex::new(cargo_process_handle)));
1180    //     Ok(pid)
1181    // }
1182
1183    pub fn wait(self: Arc<Self>, pid: Option<u32>) -> anyhow::Result<CargoProcessResult> {
1184        let mut global = GLOBAL_CHILDREN.lock().unwrap();
1185        if let Some(pid) = pid {
1186            // Lock the global list of processes and attempt to find the cargo process handle directly by pid
1187            if let Some(cargo_process_handle) = global.get_mut(&pid) {
1188                let mut cargo_process_handle = cargo_process_handle.lock().unwrap();
1189
1190                // Wait for the process to finish and retrieve the result
1191                // println!("Waiting for process with PID: {}", pid);
1192                // let result = cargo_process_handle.wait();
1193                // println!("Process with PID {} finished", pid);
1194                loop {
1195                    println!("Waiting for process with PID: {}", pid);
1196
1197                    // Attempt to wait for the process, but don't block indefinitely
1198                    let status = cargo_process_handle.child.try_wait()?;
1199
1200                    // If the status is `Some(status)`, the process has finished
1201                    if let Some(status) = status {
1202                        if status.code() == Some(101) {
1203                            println!("Process with PID {} finished with cargo error", pid);
1204                        }
1205
1206                        // Check the terminal error flag and update the result if there is an error
1207                        if *cargo_process_handle.terminal_error_flag.lock().unwrap()
1208                            != TerminalError::NoError
1209                        {
1210                            let terminal_error =
1211                                *cargo_process_handle.terminal_error_flag.lock().unwrap();
1212                            cargo_process_handle.result.terminal_error = Some(terminal_error);
1213                        }
1214
1215                        let final_diagnostics = {
1216                            let diag_lock = self.diagnostics.lock().unwrap();
1217                            diag_lock.clone()
1218                        };
1219                        cargo_process_handle.result.diagnostics = final_diagnostics.clone();
1220                        cargo_process_handle.result.exit_status = Some(status);
1221                        cargo_process_handle.result.end_time = Some(SystemTime::now());
1222                        cargo_process_handle.result.elapsed_time = Some(
1223                            cargo_process_handle
1224                                .result
1225                                .end_time
1226                                .unwrap()
1227                                .duration_since(cargo_process_handle.result.start_time.unwrap())
1228                                .unwrap(),
1229                        );
1230                        println!(
1231                            "Process with PID {} finished {:?} {}",
1232                            pid,
1233                            status,
1234                            final_diagnostics.len()
1235                        );
1236                        return Ok(cargo_process_handle.result.clone());
1237                        // return Ok(CargoProcessResult { exit_status: status, ..Default::default() });
1238                    }
1239
1240                    // Sleep briefly to yield control back to the system and avoid blocking
1241                    std::thread::sleep(std::time::Duration::from_secs(1));
1242                }
1243
1244                // Return the result
1245                // match result {
1246                //     Ok(res) => Ok(res),
1247                //     Err(e) => Err(anyhow::anyhow!("Failed to wait for cargo process: {}", e).into()),
1248                // }
1249            } else {
1250                Err(anyhow::anyhow!(
1251                    "Process handle with PID {} not found in GLOBAL_CHILDREN",
1252                    pid
1253                )
1254                .into())
1255            }
1256        } else {
1257            Err(anyhow::anyhow!("No PID provided for waiting on cargo process").into())
1258        }
1259    }
1260
1261    // pub fn run_wait(self: Arc<Self>) -> anyhow::Result<CargoProcessResult> {
1262    //     // Run the cargo command and get the process handle (non-blocking)
1263    //     let pid = self.clone().run()?; // adds to global list of processes
1264    //     let result = self.wait(Some(pid)); // Wait for the process to finish
1265    //     // Remove the completed process from GLOBAL_CHILDREN
1266    //     let mut global = GLOBAL_CHILDREN.lock().unwrap();
1267    //     global.remove(&pid);
1268
1269    //     result
1270    // }
1271
1272    // Runs the cargo command using the builder's configuration.
1273    // pub fn run(&self) -> anyhow::Result<CargoProcessResult> {
1274    //     // Build the command using the builder's configuration
1275    //     let mut command = self.build_command();
1276
1277    //     // Now use the `spawn_cargo_capture` extension to run the command
1278    //     let mut cargo_process_handle = command.spawn_cargo_capture(
1279    //         self.stdout_dispatcher.clone(),
1280    //         self.stderr_dispatcher.clone(),
1281    //         self.progress_dispatcher.clone(),
1282    //         self.stage_dispatcher.clone(),
1283    //         None,
1284    //     );
1285
1286    //     // Wait for the process to finish and retrieve the results
1287    //     cargo_process_handle.wait().context("Failed to execute cargo process")
1288    // }
1289
1290    /// Configure the command based on the target kind.
1291    pub fn with_target(mut self, target: &CargoTarget) -> Self {
1292        if let Some(origin) = target.origin.clone() {
1293            println!("Target origin: {:?}", origin);
1294        } else {
1295            println!("Target origin is not set");
1296        }
1297        match target.kind {
1298            TargetKind::Unknown | TargetKind::Plugin => {
1299                return self;
1300            }
1301            TargetKind::Bench => {
1302                // // To run benchmarks, use the "bench" command.
1303                //  let exe_path = match which("bench") {
1304                //     Ok(path) => path,
1305                //     Err(err) => {
1306                //         eprintln!("Error: 'trunk' not found in PATH: {}", err);
1307                //         return self;
1308                //     }
1309                // };
1310                // self.alternate_cmd = Some("bench".to_string())
1311                self.args.push("bench".into());
1312                self.args.push(target.name.clone());
1313            }
1314            TargetKind::Test => {
1315                self.args.push("test".into());
1316                // Pass the target's name as a filter to run specific tests.
1317                self.args.push(target.name.clone());
1318            }
1319            TargetKind::UnknownExample
1320            | TargetKind::UnknownExtendedExample
1321            | TargetKind::Example
1322            | TargetKind::ExtendedExample => {
1323                self.args.push(self.subcommand.clone());
1324                //self.args.push("--message-format=json".into());
1325                self.args.push("--example".into());
1326                self.args.push(target.name.clone());
1327                self.args.push("--manifest-path".into());
1328                self.args.push(
1329                    target
1330                        .manifest_path
1331                        .clone()
1332                        .to_str()
1333                        .unwrap_or_default()
1334                        .to_owned(),
1335                );
1336                self = self.with_required_features(&target.manifest_path, target);
1337            }
1338            TargetKind::UnknownBinary
1339            | TargetKind::UnknownExtendedBinary
1340            | TargetKind::Binary
1341            | TargetKind::ExtendedBinary => {
1342                self.args.push(self.subcommand.clone());
1343                self.args.push("--bin".into());
1344                self.args.push(target.name.clone());
1345                self.args.push("--manifest-path".into());
1346                self.args.push(
1347                    target
1348                        .manifest_path
1349                        .clone()
1350                        .to_str()
1351                        .unwrap_or_default()
1352                        .to_owned(),
1353                );
1354                self = self.with_required_features(&target.manifest_path, target);
1355            }
1356            TargetKind::Manifest => {
1357                self.suppressed_flags.insert("quiet".to_string());
1358                self.args.push(self.subcommand.clone());
1359                self.args.push("--manifest-path".into());
1360                self.args.push(
1361                    target
1362                        .manifest_path
1363                        .clone()
1364                        .to_str()
1365                        .unwrap_or_default()
1366                        .to_owned(),
1367                );
1368            }
1369            TargetKind::ManifestTauriExample => {
1370                self.suppressed_flags.insert("quiet".to_string());
1371                self.args.push(self.subcommand.clone());
1372                self.args.push("--example".into());
1373                self.args.push(target.name.clone());
1374                self.args.push("--manifest-path".into());
1375                self.args.push(
1376                    target
1377                        .manifest_path
1378                        .clone()
1379                        .to_str()
1380                        .unwrap_or_default()
1381                        .to_owned(),
1382                );
1383                self = self.with_required_features(&target.manifest_path, target);
1384            }
1385            TargetKind::ScriptScriptisto => {
1386                let exe_path = match which("scriptisto") {
1387                    Ok(path) => path,
1388                    Err(err) => {
1389                        eprintln!("Error: 'scriptisto' not found in PATH: {}", err);
1390                        return self;
1391                    }
1392                };
1393                self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1394                let candidate_opt = match &target.origin {
1395                    Some(TargetOrigin::SingleFile(path))
1396                    | Some(TargetOrigin::DefaultBinary(path)) => Some(path),
1397                    _ => None,
1398                };
1399                if let Some(candidate) = candidate_opt {
1400                    self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1401                    self.args.push(candidate.to_string_lossy().to_string());
1402                } else {
1403                    println!("No scriptisto origin found for: {:?}", target);
1404                }
1405            }
1406            TargetKind::ScriptRustScript => {
1407                let exe_path = match crate::e_installer::ensure_rust_script() {
1408                    Ok(p) => p,
1409                    Err(e) => {
1410                        eprintln!("{}", e);
1411                        return self;
1412                    }
1413                };
1414                let candidate_opt = match &target.origin {
1415                    Some(TargetOrigin::SingleFile(path))
1416                    | Some(TargetOrigin::DefaultBinary(path)) => Some(path),
1417                    _ => None,
1418                };
1419                if let Some(candidate) = candidate_opt {
1420                    self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1421                    if self.is_filter {
1422                        self.args.push("-c".into()); // ask for cargo output
1423                    }
1424                    self.args.push(candidate.to_string_lossy().to_string());
1425                } else {
1426                    println!("No rust-script origin found for: {:?}", target);
1427                }
1428            }
1429            TargetKind::ManifestTauri => {
1430                // First, locate the Cargo.toml using the existing function
1431                let manifest_path = crate::locate_manifest(true).unwrap_or_else(|_| {
1432                    eprintln!("Error: Unable to locate Cargo.toml file.");
1433                    std::process::exit(1);
1434                });
1435
1436                // Now, get the workspace parent from the manifest directory
1437                let manifest_dir = Path::new(&manifest_path)
1438                    .parent()
1439                    .unwrap_or(Path::new(".."));
1440
1441                // Ensure npm dependencies are handled at the workspace parent level
1442                let pnpm =
1443                    crate::e_installer::check_pnpm_and_install(manifest_dir).unwrap_or_else(|_| {
1444                        eprintln!("Error: Unable to check pnpm dependencies.");
1445                        PathBuf::new()
1446                    });
1447                if pnpm == PathBuf::new() {
1448                    crate::e_installer::check_npm_and_install(manifest_dir).unwrap_or_else(|_| { 
1449                        eprintln!("Error: Unable to check npm dependencies.");
1450                    });
1451                }
1452
1453                self.suppressed_flags.insert("quiet".to_string());
1454                // Helper closure to check for tauri.conf.json in a directory.
1455                let has_tauri_conf = |dir: &Path| -> bool { dir.join("tauri.conf.json").exists() };
1456
1457                // Helper closure to check for tauri.conf.json and package.json in a directory.
1458                let has_file = |dir: &Path, filename: &str| -> bool { dir.join(filename).exists() };
1459                // Try candidate's parent (if origin is SingleFile or DefaultBinary).
1460                let candidate_dir_opt = match &target.origin {
1461                    Some(TargetOrigin::SingleFile(path))
1462                    | Some(TargetOrigin::DefaultBinary(path)) => path.parent(),
1463                    _ => None,
1464                };
1465
1466                if let Some(candidate_dir) = candidate_dir_opt {
1467                    if has_tauri_conf(candidate_dir) {
1468                        println!("Using candidate directory: {}", candidate_dir.display());
1469                        self.execution_dir = Some(candidate_dir.to_path_buf());
1470                    } else if let Some(manifest_parent) = target.manifest_path.parent() {
1471                        if has_tauri_conf(manifest_parent) {
1472                            println!("Using manifest parent: {}", manifest_parent.display());
1473                            self.execution_dir = Some(manifest_parent.to_path_buf());
1474                        } else if let Some(grandparent) = manifest_parent.parent() {
1475                            if has_tauri_conf(grandparent) {
1476                                println!("Using manifest grandparent: {}", grandparent.display());
1477                                self.execution_dir = Some(grandparent.to_path_buf());
1478                            } else {
1479                                println!("No tauri.conf.json found in candidate, manifest parent, or grandparent; defaulting to manifest parent: {}", manifest_parent.display());
1480                                self.execution_dir = Some(manifest_parent.to_path_buf());
1481                            }
1482                        } else {
1483                            println!("No grandparent for manifest; defaulting to candidate directory: {}", candidate_dir.display());
1484                            self.execution_dir = Some(candidate_dir.to_path_buf());
1485                        }
1486                    } else {
1487                        println!(
1488                            "No manifest parent found for: {}",
1489                            target.manifest_path.display()
1490                        );
1491                    }
1492                    // Check for package.json and run npm ls if found.
1493                    println!("Checking for package.json in: {}", candidate_dir.display());
1494                    if has_file(candidate_dir, "package.json") {
1495                        crate::e_installer::check_npm_and_install(candidate_dir).ok();
1496                    }
1497                } else if let Some(manifest_parent) = target.manifest_path.parent() {
1498                    if has_tauri_conf(manifest_parent) {
1499                        println!("Using manifest parent: {}", manifest_parent.display());
1500                        self.execution_dir = Some(manifest_parent.to_path_buf());
1501                    } else if let Some(grandparent) = manifest_parent.parent() {
1502                        if has_tauri_conf(grandparent) {
1503                            println!("Using manifest grandparent: {}", grandparent.display());
1504                            self.execution_dir = Some(grandparent.to_path_buf());
1505                        } else {
1506                            println!(
1507                                "No tauri.conf.json found; defaulting to manifest parent: {}",
1508                                manifest_parent.display()
1509                            );
1510                            self.execution_dir = Some(manifest_parent.to_path_buf());
1511                        }
1512                    }
1513                    // Check for package.json and run npm ls if found.
1514                    println!(
1515                        "Checking for package.json in: {}",
1516                        manifest_parent.display()
1517                    );
1518                    if has_file(manifest_parent, "package.json") {
1519                        crate::e_installer::check_npm_and_install(manifest_parent).ok();
1520                    }
1521                    if has_file(Path::new("."), "package.json") {
1522                        crate::e_installer::check_npm_and_install(manifest_parent).ok();
1523                    }
1524                } else {
1525                    println!(
1526                        "No manifest parent found for: {}",
1527                        target.manifest_path.display()
1528                    );
1529                }
1530                self.args.push("tauri".into());
1531                self.args.push("dev".into());
1532            }
1533            TargetKind::ManifestLeptos => {
1534                let readme_path = target
1535                    .manifest_path
1536                    .parent()
1537                    .map(|p| p.join("README.md"))
1538                    .filter(|p| p.exists())
1539                    .or_else(|| {
1540                        target
1541                            .manifest_path
1542                            .parent()
1543                            .map(|p| p.join("readme.md"))
1544                            .filter(|p| p.exists())
1545                    });
1546
1547                if let Some(readme) = readme_path {
1548                    if let Ok(mut file) = std::fs::File::open(&readme) {
1549                        let mut contents = String::new();
1550                        if file.read_to_string(&mut contents).is_ok()
1551                            && contents.contains("cargo leptos watch")
1552                        {
1553                            // Use cargo leptos watch
1554                            println!("Detected 'cargo leptos watch' in {}", readme.display());
1555                            crate::e_installer::ensure_leptos().unwrap_or_else(|_| {
1556                                eprintln!("Error: Unable to ensure leptos installation.");
1557                                PathBuf::new() // Return an empty PathBuf as a fallback
1558                            });
1559                            self.execution_dir =
1560                                target.manifest_path.parent().map(|p| p.to_path_buf());
1561
1562                            self.alternate_cmd = Some("cargo".to_string());
1563                            self.args.push("leptos".into());
1564                            self.args.push("watch".into());
1565                            self = self.with_required_features(&target.manifest_path, target);
1566                            if let Some(exec_dir) = &self.execution_dir {
1567                                if exec_dir.join("package.json").exists() {
1568                                    println!(
1569                                        "Found package.json in execution directory: {}",
1570                                        exec_dir.display()
1571                                    );
1572                                    crate::e_installer::check_npm_and_install(exec_dir).ok();
1573                                }
1574                            }
1575                            return self;
1576                        }
1577                    }
1578                }
1579
1580                // fallback to trunk
1581                let exe_path = match crate::e_installer::ensure_trunk() {
1582                    Ok(p) => p,
1583                    Err(e) => {
1584                        eprintln!("{}", e);
1585                        return self;
1586                    }
1587                };
1588
1589                if let Some(manifest_parent) = target.manifest_path.parent() {
1590                    println!("Manifest path: {}", target.manifest_path.display());
1591                    println!(
1592                        "Execution directory (same as manifest folder): {}",
1593                        manifest_parent.display()
1594                    );
1595                    self.execution_dir = Some(manifest_parent.to_path_buf());
1596                } else {
1597                    println!(
1598                        "No manifest parent found for: {}",
1599                        target.manifest_path.display()
1600                    );
1601                }
1602                if let Some(exec_dir) = &self.execution_dir {
1603                    if exec_dir.join("package.json").exists() {
1604                        println!(
1605                            "Found package.json in execution directory: {}",
1606                            exec_dir.display()
1607                        );
1608                        crate::e_installer::check_npm_and_install(exec_dir).ok();
1609                    }
1610                }
1611                self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1612                self.args.push("serve".into());
1613                self.args.push("--open".into());
1614                self.args.push("--color".into());
1615                self.args.push("always".into());
1616                self = self.with_required_features(&target.manifest_path, target);
1617            }
1618            TargetKind::ManifestDioxus => {
1619                // For Dioxus targets, print the manifest path and set the execution directory
1620                let exe_path = match crate::e_installer::ensure_dx() {
1621                    Ok(path) => path,
1622                    Err(e) => {
1623                        eprintln!("Error locating `dx`: {}", e);
1624                        return self;
1625                    }
1626                };
1627                // to be the same directory as the manifest.
1628                if let Some(manifest_parent) = target.manifest_path.parent() {
1629                    println!("Manifest path: {}", target.manifest_path.display());
1630                    println!(
1631                        "Execution directory (same as manifest folder): {}",
1632                        manifest_parent.display()
1633                    );
1634                    self.execution_dir = Some(manifest_parent.to_path_buf());
1635                } else {
1636                    println!(
1637                        "No manifest parent found for: {}",
1638                        target.manifest_path.display()
1639                    );
1640                }
1641                self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1642                self.args.push("serve".into());
1643                self = self.with_required_features(&target.manifest_path, target);
1644            }
1645            TargetKind::ManifestDioxusExample => {
1646                let exe_path = match crate::e_installer::ensure_dx() {
1647                    Ok(path) => path,
1648                    Err(e) => {
1649                        eprintln!("Error locating `dx`: {}", e);
1650                        return self;
1651                    }
1652                };
1653                // For Dioxus targets, print the manifest path and set the execution directory
1654                // to be the same directory as the manifest.
1655                if let Some(manifest_parent) = target.manifest_path.parent() {
1656                    println!("Manifest path: {}", target.manifest_path.display());
1657                    println!(
1658                        "Execution directory (same as manifest folder): {}",
1659                        manifest_parent.display()
1660                    );
1661                    self.execution_dir = Some(manifest_parent.to_path_buf());
1662                } else {
1663                    println!(
1664                        "No manifest parent found for: {}",
1665                        target.manifest_path.display()
1666                    );
1667                }
1668                self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1669                self.args.push("serve".into());
1670                self.args.push("--example".into());
1671                self.args.push(target.name.clone());
1672                self = self.with_required_features(&target.manifest_path, target);
1673            }
1674        }
1675        self
1676    }
1677
1678    /// Configure the command using CLI options.
1679    pub fn with_cli(mut self, cli: &crate::Cli) -> Self {
1680        if cli.quiet && !self.suppressed_flags.contains("quiet") {
1681            // Insert --quiet right after "run" if present.
1682            if let Some(pos) = self.args.iter().position(|arg| arg == &self.subcommand) {
1683                self.args.insert(pos + 1, "--quiet".into());
1684            } else {
1685                self.args.push("--quiet".into());
1686            }
1687        }
1688        if cli.release {
1689            // Insert --release right after the initial "run" command if applicable.
1690            // For example, if the command already contains "run", insert "--release" after it.
1691            if let Some(pos) = self.args.iter().position(|arg| arg == &self.subcommand) {
1692                self.args.insert(pos + 1, "--release".into());
1693            } else {
1694                // If not running a "run" command (like in the Tauri case), simply push it.
1695                self.args.push("--release".into());
1696            }
1697        }
1698        // Append extra arguments (if any) after a "--" separator.
1699        if !cli.extra.is_empty() {
1700            self.args.push("--".into());
1701            self.args.extend(cli.extra.iter().cloned());
1702        }
1703        self
1704    }
1705    /// Append required features based on the manifest, target kind, and name.
1706    /// This method queries your manifest helper function and, if features are found,
1707    /// appends "--features" and the feature list.
1708    pub fn with_required_features(mut self, manifest: &PathBuf, target: &CargoTarget) -> Self {
1709        if !self.args.contains(&"--features".to_string()) {
1710            if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
1711                manifest,
1712                &target.kind,
1713                &target.name,
1714            ) {
1715                self.args.push("--features".to_string());
1716                self.args.push(features);
1717            }
1718        }
1719        self
1720    }
1721
1722    /// Appends extra arguments to the command.
1723    pub fn with_extra_args(mut self, extra: &[String]) -> Self {
1724        if !extra.is_empty() {
1725            // Use "--" to separate Cargo arguments from target-specific arguments.
1726            self.args.push("--".into());
1727            self.args.extend(extra.iter().cloned());
1728        }
1729        self
1730    }
1731
1732    /// Builds the final vector of command-line arguments.
1733    pub fn build(self) -> Vec<String> {
1734        self.args
1735    }
1736
1737    pub fn is_compiler_target(&self) -> bool {
1738        let supported_subcommands = ["run", "build", "check", "leptos", "tauri"];
1739        if let Some(alternate) = &self.alternate_cmd {
1740            if alternate == "trunk" {
1741                return true;
1742            }
1743            if alternate != "cargo" {
1744                return false;
1745            }
1746        }
1747        if let Some(_) = self
1748            .args
1749            .iter()
1750            .position(|arg| supported_subcommands.contains(&arg.as_str()))
1751        {
1752            return true;
1753        }
1754        false
1755    }
1756
1757    pub fn injected_args(&self) -> (String, Vec<String>) {
1758        let mut new_args = self.args.clone();
1759        let supported_subcommands = [
1760            "run", "build", "test", "bench", "clean", "doc", "publish", "update",
1761        ];
1762
1763        if self.is_filter {
1764            if let Some(pos) = new_args
1765                .iter()
1766                .position(|arg| supported_subcommands.contains(&arg.as_str()))
1767            {
1768                // If the command is a supported subcommand like "cargo run", insert the JSON output format and color options.
1769                new_args.insert(pos + 1, "--message-format=json".into());
1770                new_args.insert(pos + 2, "--color".into());
1771                new_args.insert(pos + 3, "always".into());
1772            }
1773        }
1774
1775        let program = self.alternate_cmd.as_deref().unwrap_or("cargo").to_string();
1776        (program, new_args)
1777    }
1778
1779    pub fn print_command(&self) {
1780        let (program, new_args) = self.injected_args();
1781        println!("{} {}", program, new_args.join(" "));
1782    }
1783
1784    /// builds a std::process::Command.
1785    pub fn build_command(&self) -> Command {
1786        let (program, new_args) = self.injected_args();
1787
1788        let mut cmd = Command::new(program);
1789        cmd.args(new_args);
1790
1791        if let Some(dir) = &self.execution_dir {
1792            cmd.current_dir(dir);
1793        }
1794
1795        cmd
1796    }
1797    /// Runs the command and returns everything it printed (stdout + stderr),
1798    /// regardless of exit status.
1799    pub fn capture_output(&self) -> anyhow::Result<String> {
1800        // Build and run
1801        let mut cmd = self.build_command();
1802        let output = cmd
1803            .output()
1804            .map_err(|e| anyhow::anyhow!("Failed to spawn cargo process: {}", e))?;
1805
1806        // Decode both stdout and stderr lossily
1807        let mut all = String::new();
1808        all.push_str(&String::from_utf8_lossy(&output.stdout));
1809        all.push_str(&String::from_utf8_lossy(&output.stderr));
1810
1811        // Return the combined string, even if exit was !success
1812        Ok(all)
1813    }
1814}
1815/// Resolves a file path by:
1816///   1. If the path is relative, try to resolve it relative to the current working directory.
1817///   2. If that file does not exist, try to resolve it relative to the parent directory of the manifest path.
1818///   3. Otherwise, return the original relative path.
1819pub(crate) fn resolve_file_path(manifest_path: &PathBuf, file_str: &str) -> PathBuf {
1820    let file_path = Path::new(file_str);
1821    if file_path.is_relative() {
1822        // 1. Try resolving relative to the current working directory.
1823        if let Ok(cwd) = env::current_dir() {
1824            let cwd_path = cwd.join(file_path);
1825            if cwd_path.exists() {
1826                return cwd_path;
1827            }
1828        }
1829        // 2. Try resolving relative to the parent of the manifest path.
1830        if let Some(manifest_parent) = manifest_path.parent() {
1831            let parent_path = manifest_parent.join(file_path);
1832            if parent_path.exists() {
1833                return parent_path;
1834            }
1835        }
1836        // 3. Neither existed; return the relative path as-is.
1837        return file_path.to_path_buf();
1838    }
1839    file_path.to_path_buf()
1840}
1841
1842// --- Example usage ---
1843#[cfg(test)]
1844mod tests {
1845    use crate::e_target::TargetOrigin;
1846
1847    use super::*;
1848
1849    #[test]
1850    fn test_command_builder_example() {
1851        let target_name = "my_example".to_string();
1852        let target = CargoTarget {
1853            name: "my_example".to_string(),
1854            display_name: "My Example".to_string(),
1855            manifest_path: "Cargo.toml".into(),
1856            kind: TargetKind::Example,
1857            extended: true,
1858            toml_specified: false,
1859            origin: Some(TargetOrigin::SingleFile(PathBuf::from(
1860                "examples/my_example.rs",
1861            ))),
1862        };
1863
1864        let extra_args = vec!["--flag".to_string(), "value".to_string()];
1865
1866        let manifest_path = PathBuf::from("Cargo.toml");
1867        let args = CargoCommandBuilder::new(&target_name, &manifest_path, "run", false)
1868            .with_target(&target)
1869            .with_extra_args(&extra_args)
1870            .build();
1871
1872        // For an example target, we expect something like:
1873        // cargo run --example my_example --manifest-path Cargo.toml -- --flag value
1874        assert!(args.contains(&"--example".to_string()));
1875        assert!(args.contains(&"my_example".to_string()));
1876        assert!(args.contains(&"--manifest-path".to_string()));
1877        assert!(args.contains(&"Cargo.toml".to_string()));
1878        assert!(args.contains(&"--".to_string()));
1879        assert!(args.contains(&"--flag".to_string()));
1880        assert!(args.contains(&"value".to_string()));
1881    }
1882}