cargo_e/
e_runner.rs

1use crate::e_processmanager::ProcessManager;
2use crate::{e_target::TargetOrigin, prelude::*};
3// #[cfg(not(feature = "equivalent"))]
4// use ctrlc;
5use crate::e_cargocommand_ext::CargoProcessHandle;
6use crate::e_target::CargoTarget;
7use anyhow::Result;
8use once_cell::sync::Lazy;
9use std::collections::HashMap;
10use std::fs::File;
11use std::io::{self, BufRead};
12use std::path::Path;
13use std::process::Command;
14use std::sync::atomic::{AtomicUsize, Ordering};
15use std::thread;
16use which::which; // Adjust the import based on your project structure
17
18// lazy_static! {
19//     pub static ref GLOBAL_CHILDREN: Arc<Mutex<Vec<Arc<CargoProcessHandle>>>> = Arc::new(Mutex::new(Vec::new()));
20//     static CTRL_C_COUNT: Lazy<Mutex<u32>> = Lazy::new(|| Mutex::new(0));
21// }
22
23// pub static GLOBAL_CHILDREN:     Lazy<Arc<Mutex<Vec<Arc<Mutex<CargoProcessHandle>>>>>> = Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
24pub static GLOBAL_CHILDREN: Lazy<Arc<Mutex<HashMap<u32, Arc<Mutex<CargoProcessHandle>>>>>> =
25    Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
26
27static CTRL_C_COUNT: AtomicUsize = AtomicUsize::new(0);
28
29// Global shared container for the currently running child process.
30// pub static GLOBAL_CHILD: Lazy<Arc<Mutex<Option<Child>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
31// static CTRL_C_COUNT: Lazy<Mutex<u32>> = Lazy::new(|| Mutex::new(0));
32
33// pub static GLOBAL_CHILDREN: Lazy<Arc<Mutex<VecDeque<CargoProcessHandle>>>> = Lazy::new(|| Arc::new(Mutex::new(VecDeque::new())));
34/// Resets the Ctrl+C counter.
35/// This can be called to reset the count when starting a new program or at any other point.
36pub fn reset_ctrl_c_count() {
37    CTRL_C_COUNT.store(0, std::sync::atomic::Ordering::SeqCst);
38}
39
40// pub fn kill_last_process() -> Result<()> {
41//     let mut global = GLOBAL_CHILDREN.lock().unwrap();
42
43//     if let Some(mut child_handle) = global.pop_back() {
44//         // Kill the most recent process
45//         eprintln!("Killing the most recent child process...");
46//         let _ = child_handle.kill();
47//         Ok(())
48//     } else {
49//         eprintln!("No child processes to kill.");
50//         Err(anyhow::anyhow!("No child processes to kill").into())
51//     }
52// }
53
54pub fn take_process_results(pid: u32) -> Option<CargoProcessHandle> {
55    let mut global = GLOBAL_CHILDREN.lock().ok()?;
56    // Take ownership
57    // let handle = global.remove(&pid)?;
58    // let mut handle = handle.lock().ok()?;
59    let handle = global.remove(&pid)?;
60    // global.remove(&pid)
61    // This will succeed only if no other Arc exists
62    Arc::try_unwrap(handle)
63        .ok()? // fails if other Arc exists
64        .into_inner()
65        .ok() // fails if poisoned
66}
67
68pub fn get_process_results_in_place(
69    pid: u32,
70) -> Option<crate::e_cargocommand_ext::CargoProcessResult> {
71    let global = GLOBAL_CHILDREN.lock().ok()?; // MutexGuard<HashMap>
72    let handle = global.get(&pid)?.clone(); // Arc<Mutex<CargoProcessHandle>>
73    let handle = handle.lock().ok()?; // MutexGuard<CargoProcessHandle>
74    Some(handle.result.clone()) // ✅ return the result field
75}
76
77// /// Registers a global Ctrl+C handler that interacts with the `GLOBAL_CHILDREN` process container.
78// pub fn register_ctrlc_handler() -> Result<(), Box<dyn Error>> {
79//     println!("Registering Ctrl+C handler...");
80//     ctrlc::set_handler(move || {
81//          let count = CTRL_C_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
82//         {
83//             eprintln!("Ctrl+C pressed");
84
85//     // lock only ONE mutex safely
86//     if let Ok(mut global) = GLOBAL_CHILDREN.try_lock() {
87//             // let mut global = GLOBAL_CHILDREN.lock().unwrap();
88//             eprintln!("Ctrl+C got lock on global container");
89
90//             // If there are processes in the global container, terminate the most recent one
91//             if let Some((pid, child_handle)) = global.iter_mut().next() {
92//                 eprintln!("Ctrl+C pressed, terminating the child process with PID: {}", pid);
93
94//                 // Lock the child process and kill it
95//                 let mut child_handle = child_handle.lock().unwrap();
96//                 if child_handle.requested_exit {
97//                     eprintln!("Child process is already requested kill...");
98//                 } else {
99//                     eprintln!("Child process is not running, no need to kill.");
100//                     child_handle.requested_exit=true;
101//                     println!("Killing child process with PID: {}", pid);
102//                     let _ = child_handle.kill();  // Attempt to kill the process
103//                     println!("Killed child process with PID: {}", pid);
104
105//                     reset_ctrl_c_count();
106//                     return;  // Exit after successfully terminating the process
107//                 }
108
109//                 // Now remove the process from the global container
110//                 // let pid_to_remove = *pid;
111
112//                 // // Reacquire the lock after killing and remove the process from global
113//                 // drop(global);  // Drop the first borrow
114
115//                 // // Re-lock global and safely remove the entry using the pid
116//                 // let mut global = GLOBAL_CHILDREN.lock().unwrap();
117//                 // global.remove(&pid_to_remove); // Remove the process entry by PID
118//                 // println!("Removed process with PID: {}", pid_to_remove);
119//             }
120
121//     } else {
122//         eprintln!("Couldn't acquire GLOBAL_CHILDREN lock safely");
123//     }
124
125/// Registers a global Ctrl+C handler that uses the process manager.
126pub fn register_ctrlc_handler(process_manager: Arc<ProcessManager>) -> Result<(), Box<dyn Error>> {
127    println!("Registering Ctrl+C handler...");
128    ctrlc::set_handler(move || {
129        let count = CTRL_C_COUNT.fetch_add(1, Ordering::SeqCst) + 1;
130        eprintln!("Ctrl+C pressed");
131
132        // Use the process manager's API to handle killing
133        match process_manager.kill_one() {
134            Ok(true) => {
135                eprintln!("Process was successfully terminated.");
136                reset_ctrl_c_count();
137                return; // Exit handler early after a successful kill.
138            }
139            Ok(false) => {
140                eprintln!("No process was killed this time.");
141            }
142            Err(e) => {
143                eprintln!("Error killing process: {:?}", e);
144            }
145        }
146
147        // Handle Ctrl+C count logic for exiting the program.
148        if count == 3 {
149            eprintln!("Ctrl+C pressed 3 times with no child process running. Exiting.");
150            std::process::exit(0);
151        } else if count == 2 {
152            eprintln!("Ctrl+C pressed 2 times, press one more to exit.");
153        } else {
154            eprintln!("Ctrl+C pressed {} times, no child process running.", count);
155        }
156    })?;
157    Ok(())
158}
159
160//         }
161
162//         // Now handle the Ctrl+C count and display messages
163//         // If Ctrl+C is pressed 3 times without any child process, exit the program.
164//         if count == 3 {
165//             eprintln!("Ctrl+C pressed 3 times with no child process running. Exiting.");
166//             std::process::exit(0);
167//         } else if count == 2 {
168//             // Notify that one more Ctrl+C will exit the program.
169//             eprintln!("Ctrl+C pressed 2 times, press one more to exit.");
170//         } else {
171//             eprintln!("Ctrl+C pressed {} times, no child process running.", count);
172//         }
173//     })?;
174//     Ok(())
175// }
176
177// /// Registers a global Ctrl+C handler once.
178// /// The handler checks GLOBAL_CHILD and kills the child process if present.
179// pub fn register_ctrlc_handler() -> Result<(), Box<dyn Error>> {
180//     ctrlc::set_handler(move || {
181//         let mut count_lock = CTRL_C_COUNT.lock().unwrap();
182//         *count_lock += 1;
183
184//         let count = *count_lock;
185
186//         // If there is no child process and Ctrl+C is pressed 3 times, exit the program
187//         if count == 3 {
188//             eprintln!("Ctrl+C pressed 3 times with no child process running. Exiting.");
189//             exit(0);
190//         } else {
191//             let mut child_lock = GLOBAL_CHILD.lock().unwrap();
192//             if let Some(child) = child_lock.as_mut() {
193//                 eprintln!(
194//                     "Ctrl+C pressed {} times, terminating running child process...",
195//                     count
196//                 );
197//                 let _ = child.kill();
198//             } else {
199//                 eprintln!("Ctrl+C pressed {} times, no child process running.", count);
200//             }
201//         }
202//     })?;
203//     Ok(())
204// }
205
206/// Asynchronously launches the GenAI summarization example for the given target.
207/// It builds the command using the target's manifest path as the "origin" argument.
208pub async fn open_ai_summarize_for_target(target: &CargoTarget) {
209    // Extract the origin path from the target (e.g. the manifest path).
210    let origin_path = match &target.origin {
211        Some(TargetOrigin::SingleFile(path)) | Some(TargetOrigin::DefaultBinary(path)) => path,
212        _ => return,
213    };
214
215    let exe_path = match which("cargoe_ai_summarize") {
216        Ok(path) => path,
217        Err(err) => {
218            eprintln!("Error: 'cargoe_ai_summarize' not found in PATH: {}", err);
219            return;
220        }
221    };
222    // Build the command based on the platform.
223    // let mut cmd = if cfg!(target_os = "windows") {
224    //     let command_str = format!(
225    //         "e_ai_summarize --streaming --stdin {}",
226    //         origin_path.as_os_str().to_string_lossy()
227    //     );
228    //     println!("Running command: {}", command_str);
229    //     let mut command = Command::new("cmd");
230    //     command.args(["/C", &command_str]);
231    //     command
232    // } else {
233    let mut cmd = Command::new(exe_path);
234    cmd.arg("--streaming");
235    cmd.arg("--stdin");
236    // cmd.arg(".");
237    cmd.arg(origin_path);
238    // command
239    // };
240
241    cmd.stdin(Stdio::inherit())
242        .stdout(Stdio::inherit())
243        .stderr(Stdio::inherit());
244
245    // Spawn the command and wait for it to finish.
246    let child = cmd.spawn();
247    let status = child
248        .expect("Failed to spawn command")
249        .wait()
250        .expect("Failed to wait for command");
251
252    if !status.success() {
253        eprintln!("Command exited with status: {}", status);
254    }
255
256    // // Build the command to run the example.
257    // let output = if cfg!(target_os = "windows") {
258    //     let command_str = format!("e_ai_summarize --stdin {}", origin_path.as_os_str().to_string_lossy());
259    //     println!("Running command: {}", command_str);
260    //     Command::new("cmd")
261    //         .args([
262    //             "/C",
263    //             command_str.as_str(),
264    //         ])
265    //         .output()
266    // } else {
267    //     Command::new("e_ai_summarize")
268    //         .args([origin_path])
269    //         .output()
270    // };
271
272    // // Handle the output from the command.
273    // match output {
274    //     Ok(output) if output.status.success() => {
275    //         // The summarization example ran successfully.
276    //         println!("----
277    //         {}", String::from_utf8_lossy(&output.stdout));
278    //     }
279    //     Ok(output) => {
280    //         let msg = format!(
281    //             "Error running summarization example:\nstdout: {}\nstderr: {}",
282    //             String::from_utf8_lossy(&output.stdout),
283    //             String::from_utf8_lossy(&output.stderr)
284    //         );
285    //         error!("{}", msg);
286    //     }
287    //     Err(e) => {
288    //         let msg = format!("Failed to execute summarization command: {}", e);
289    //         error!("{}", msg);
290    //     }
291    // }
292}
293
294/// In "equivalent" mode, behave exactly like "cargo run --example <name>"
295#[cfg(feature = "equivalent")]
296pub fn run_equivalent_example(
297    cli: &crate::Cli,
298) -> Result<std::process::ExitStatus, Box<dyn Error>> {
299    // In "equivalent" mode, behave exactly like "cargo run --example <name>"
300    let mut cmd = Command::new("cargo");
301    cmd.args([
302        "run",
303        "--example",
304        cli.explicit_example.as_deref().unwrap_or(""),
305    ]);
306    if !cli.extra.is_empty() {
307        cmd.arg("--").args(cli.extra.clone());
308    }
309    // Inherit the standard input (as well as stdout/stderr) so that input is passed through.
310    use std::process::Stdio;
311    cmd.stdin(Stdio::inherit())
312        .stdout(Stdio::inherit())
313        .stderr(Stdio::inherit());
314
315    let status = cmd.status()?;
316    std::process::exit(status.code().unwrap_or(1));
317}
318
319/// Runs the given example (or binary) target.
320pub fn run_example(
321    manager: Arc<ProcessManager>,
322    cli: &crate::Cli,
323    target: &crate::e_target::CargoTarget,
324) -> anyhow::Result<Option<std::process::ExitStatus>> {
325    crate::e_runall::set_rustflags_if_quiet(cli.quiet);
326    // Retrieve the current package name at compile time.
327    let current_bin = env!("CARGO_PKG_NAME");
328
329    // Avoid running our own binary.
330    if target.kind == crate::e_target::TargetKind::Binary && target.name == current_bin {
331        println!(
332            "Skipping automatic run: {} is the same as the running binary",
333            target.name
334        );
335        return Ok(None);
336    }
337
338    let manifest_path = PathBuf::from(target.manifest_path.clone());
339    // Build the command using the CargoCommandBuilder.
340    let mut builder = crate::e_command_builder::CargoCommandBuilder::new(
341        &manifest_path,
342        &cli.subcommand,
343        cli.filter,
344    )
345    .with_target(target)
346    .with_required_features(&target.manifest_path, target)
347    .with_cli(cli);
348
349    if !cli.extra.is_empty() {
350        builder = builder.with_extra_args(&cli.extra);
351    }
352
353    // Build the command.
354    let mut cmd = builder.clone().build_command();
355
356    // Before spawning, determine the directory to run from.
357    // If a custom execution directory was set (e.g. for Tauri targets), that is used.
358    // Otherwise, if the target is extended, run from its parent directory.
359    if let Some(ref exec_dir) = builder.execution_dir {
360        cmd.current_dir(exec_dir);
361    } else if target.extended {
362        if let Some(dir) = target.manifest_path.parent() {
363            cmd.current_dir(dir);
364        }
365    }
366
367    // Print the full command for debugging.
368    let full_command = format!(
369        "{} {}",
370        cmd.get_program().to_string_lossy(),
371        cmd.get_args()
372            .map(|arg| arg.to_string_lossy())
373            .collect::<Vec<_>>()
374            .join(" ")
375    );
376    println!("Running: {}", full_command);
377
378    // Check if the manifest triggers the workspace error.
379    let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&target.manifest_path)?;
380
381    let pid = Arc::new(builder).run(|_pid, handle| {
382        manager.register(handle);
383    })?;
384    let result = manager.wait(pid, None)?;
385    // println!("HERE IS THE RESULT!{} {:?}",pid,manager.get(pid));
386    // println!("\n\nHERE IS THE RESULT!{} {:?}",pid,result);
387    if result.is_filter {
388        result.print_exact();
389        result.print_short();
390        result.print_compact();
391
392        // manager.print_shortened_output();
393        manager.print_prefixed_summary();
394        // manager.print_compact();
395    }
396
397    // let handle=    Arc::new(builder).run_wait()?;
398    // Spawn the process.
399    // let child = cmd.spawn()?;
400    // {
401    //     let mut global = GLOBAL_CHILD.lock().unwrap();
402    //     *global = Some(child);
403    // }
404    // let status = {
405    //     let mut global = GLOBAL_CHILD.lock().unwrap();
406    //     if let Some(mut child) = global.take() {
407    //         child.wait()?
408    //     } else {
409    //         return Err(anyhow::anyhow!("Child process missing"));
410    //     }
411    // };
412
413    // Restore the manifest if we patched it.
414    if let Some(original) = maybe_backup {
415        fs::write(&target.manifest_path, original)?;
416    }
417
418    Ok(result.exit_status)
419}
420// /// Runs an example or binary target, applying a temporary manifest patch if a workspace error is detected.
421// /// This function uses the same idea as in the collection helpers: if the workspace error is found,
422// /// we patch the manifest, run the command, and then restore the manifest.
423// pub fn run_example(
424//     target: &crate::e_target::CargoTarget,
425//     extra_args: &[String],
426// ) -> Result<std::process::ExitStatus, Box<dyn Error>> {
427//     // Retrieve the current package name (or binary name) at compile time.
428
429//     use crate::e_target::TargetKind;
430
431//     let current_bin = env!("CARGO_PKG_NAME");
432
433//     // Avoid running our own binary if the target's name is the same.
434//     if target.kind == TargetKind::Binary && target.name == current_bin {
435//         return Err(format!(
436//             "Skipping automatic run: {} is the same as the running binary",
437//             target.name
438//         )
439//         .into());
440//     }
441
442//     let mut cmd = Command::new("cargo");
443//     // Determine which manifest file is used.
444//     let manifest_path: PathBuf;
445
446//     match target.kind {
447//         TargetKind::Bench => {
448//             manifest_path = PathBuf::from(target.manifest_path.clone());
449//             cmd.args([
450//                 "bench",
451//                 "--bench",
452//                 &target.name,
453//                 "--manifest-path",
454//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
455//             ]);
456//         }
457//         TargetKind::Test => {
458//             manifest_path = PathBuf::from(target.manifest_path.clone());
459//             cmd.args([
460//                 "test",
461//                 "--test",
462//                 &target.name,
463//                 "--manifest-path",
464//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
465//             ]);
466//         }
467//         TargetKind::Manifest => {
468//             manifest_path = PathBuf::from(target.manifest_path.clone());
469//             cmd.args([
470//                 "run",
471//                 "--release",
472//                 "--manifest-path",
473//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
474//                 "-p",
475//                 &target.name,
476//             ]);
477//         }
478//         TargetKind::Example => {
479//             if target.extended {
480//                 println!(
481//                     "Running extended example in folder: examples/{}",
482//                     target.name
483//                 );
484//                 // For extended examples, assume the manifest is inside the example folder.
485//                 manifest_path = PathBuf::from(format!("examples/{}/Cargo.toml", target.name));
486//                 cmd.arg("run")
487//                     .current_dir(format!("examples/{}", target.name));
488//             } else {
489//                 manifest_path = PathBuf::from(crate::locate_manifest(false)?);
490//                 cmd.args([
491//                     "run",
492//                     "--release",
493//                     "--example",
494//                     &target.name,
495//                     "--manifest-path",
496//                     &target.manifest_path.to_str().unwrap_or_default().to_owned(),
497//                 ]);
498//             }
499//         }
500//         TargetKind::Binary => {
501//             println!("Running binary: {}", target.name);
502//             manifest_path = PathBuf::from(crate::locate_manifest(false)?);
503//             cmd.args([
504//                 "run",
505//                 "--release",
506//                 "--bin",
507//                 &target.name,
508//                 "--manifest-path",
509//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
510//             ]);
511//         }
512//         TargetKind::ExtendedBinary => {
513//             println!("Running extended binary: {}", target.name);
514//             manifest_path = PathBuf::from(crate::locate_manifest(false)?);
515//             cmd.args([
516//                 "run",
517//                 "--release",
518//                 "--manifest-path",
519//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
520//                 "--bin",
521//                 &target.name,
522//             ]);
523//         }
524//         TargetKind::ExtendedExample => {
525//             println!("Running extended example: {}", target.name);
526//             manifest_path = PathBuf::from(crate::locate_manifest(false)?);
527//             cmd.args([
528//                 "run",
529//                 "--release",
530//                 "--manifest-path",
531//                 &target.manifest_path.to_str().unwrap_or_default().to_owned(),
532//                 "--example",
533//                 &target.name,
534//             ]);
535//         }
536//         TargetKind::ManifestTauri => {
537//             println!("Running tauri: {}", target.name);
538//             // For a Tauri example, run `cargo tauri dev`
539//             manifest_path = PathBuf::from(target.manifest_path.clone());
540//             let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
541//             // Start a new command for tauri dev
542//             cmd.arg("tauri").arg("dev").current_dir(manifest_dir); // run from the folder where Cargo.toml is located
543//         }
544//         TargetKind::ManifestDioxus => {
545//             println!("Running dioxus: {}", target.name);
546//             cmd = Command::new("dx");
547//             // For a Tauri example, run `cargo tauri dev`
548//             manifest_path = PathBuf::from(target.manifest_path.clone());
549//             let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
550//             // Start a new command for tauri dev
551//             cmd.arg("serve").current_dir(manifest_dir); // run from the folder where Cargo.toml is located
552//         }
553//         TargetKind::ManifestDioxusExample => {
554//             println!("Running dioxus: {}", target.name);
555//             cmd = Command::new("dx");
556//             // For a Tauri example, run `cargo tauri dev`
557//             manifest_path = PathBuf::from(target.manifest_path.clone());
558//             let manifest_dir = PathBuf::from(manifest_path.parent().expect("expected a parent"));
559//             // Start a new command for tauri dev
560//             cmd.arg("serve")
561//                 .arg("--example")
562//                 .arg(&target.name)
563//                 .current_dir(manifest_dir); // run from the folder where Cargo.toml is located
564//         }
565//     }
566
567//     // --- Add required-features support ---
568//     // This call will search the provided manifest, and if it's a workspace,
569//     // it will search workspace members for the target.
570//     if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
571//         manifest_path.as_path(),
572//         &target.kind,
573//         &target.name,
574//     ) {
575//         cmd.args(&["--features", &features]);
576//     }
577//     // --- End required-features support ---
578
579//     if !extra_args.is_empty() {
580//         cmd.arg("--").args(extra_args);
581//     }
582
583//     let full_command = format!(
584//         "{} {}",
585//         cmd.get_program().to_string_lossy(),
586//         cmd.get_args()
587//             .map(|arg| arg.to_string_lossy())
588//             .collect::<Vec<_>>()
589//             .join(" ")
590//     );
591//     println!("Running: {}", full_command);
592
593//     // Before spawning, check if the manifest triggers the workspace error.
594//     // If so, patch it temporarily.
595//     let maybe_backup = crate::e_manifest::maybe_patch_manifest_for_run(&manifest_path)?;
596
597//     // Spawn the process.
598//     let child = cmd.spawn()?;
599//     {
600//         let mut global = GLOBAL_CHILD.lock().unwrap();
601//         *global = Some(child);
602//     }
603//     let status = {
604//         let mut global = GLOBAL_CHILD.lock().unwrap();
605//         if let Some(mut child) = global.take() {
606//             child.wait()?
607//         } else {
608//             return Err("Child process missing".into());
609//         }
610//     };
611
612//     // Restore the manifest if we patched it.
613//     if let Some(original) = maybe_backup {
614//         fs::write(&manifest_path, original)?;
615//     }
616
617//     //    println!("Process exited with status: {:?}", status.code());
618//     Ok(status)
619// }
620/// Helper function to spawn a cargo process.
621/// On Windows, this sets the CREATE_NEW_PROCESS_GROUP flag.
622pub fn spawn_cargo_process(args: &[&str]) -> Result<Child, Box<dyn Error>> {
623    // #[cfg(windows)]
624    // {
625    //     use std::os::windows::process::CommandExt;
626    //     const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
627    //     let child = Command::new("cargo")
628    //         .args(args)
629    //         .creation_flags(CREATE_NEW_PROCESS_GROUP)
630    //         .spawn()?;
631    //     Ok(child)
632    // }
633    // #[cfg(not(windows))]
634    // {
635    let child = Command::new("cargo").args(args).spawn()?;
636    Ok(child)
637    // }
638}
639
640/// Returns true if the file's a "scriptisto"
641pub fn is_active_scriptisto<P: AsRef<Path>>(path: P) -> io::Result<bool> {
642    let file = File::open(path)?;
643    let mut reader = std::io::BufReader::new(file);
644    let mut first_line = String::new();
645    reader.read_line(&mut first_line)?;
646    if !first_line.contains("scriptisto") || !first_line.starts_with("#") {
647        return Ok(false);
648    }
649    Ok(true)
650}
651
652/// Returns true if the file's a "rust-script"
653pub fn is_active_rust_script<P: AsRef<Path>>(path: P) -> io::Result<bool> {
654    let file = File::open(path)?;
655    let mut reader = std::io::BufReader::new(file);
656    let mut first_line = String::new();
657    reader.read_line(&mut first_line)?;
658    if !first_line.contains("rust-script") || !first_line.starts_with("#") {
659        return Ok(false);
660    }
661    Ok(true)
662}
663
664/// Checks if `scriptisto` is installed and suggests installation if it's not.
665pub fn check_scriptisto_installed() -> Result<std::path::PathBuf, Box<dyn Error>> {
666    let r = which("scriptisto");
667    match r {
668        Ok(_) => {
669            // installed
670        }
671        Err(e) => {
672            // scriptisto is not found in the PATH
673            eprintln!("scriptisto is not installed.");
674            println!("Suggestion: To install scriptisto, run the following command:");
675            println!("cargo install scriptisto");
676            return Err(e.into());
677        }
678    }
679    Ok(r?)
680}
681
682pub fn run_scriptisto<P: AsRef<Path>>(script_path: P, args: &[&str]) -> Option<Child> {
683    let scriptisto = check_scriptisto_installed().ok()?;
684
685    let script: &std::path::Path = script_path.as_ref();
686    let child = Command::new(scriptisto)
687        .arg(script)
688        .args(args)
689        .spawn()
690        .ok()?;
691    Some(child)
692}
693
694/// Checks if `rust-script` is installed and suggests installation if it's not.
695pub fn check_rust_script_installed() -> Result<std::path::PathBuf, Box<dyn Error>> {
696    let r = which("rust-script");
697    match r {
698        Ok(_) => {
699            // rust-script is installed
700        }
701        Err(e) => {
702            // rust-script is not found in the PATH
703            eprintln!("rust-script is not installed.");
704            println!("Suggestion: To install rust-script, run the following command:");
705            println!("cargo install rust-script");
706            return Err(e.into());
707        }
708    }
709    Ok(r?)
710}
711
712pub fn run_rust_script<P: AsRef<Path>>(script_path: P, args: &[&str]) -> Option<Child> {
713    let rust_script = check_rust_script_installed().ok()?;
714
715    let script: &std::path::Path = script_path.as_ref();
716    let child = Command::new(rust_script)
717        .arg(script)
718        .args(args)
719        .spawn()
720        .ok()?;
721    Some(child)
722}
723
724pub fn run_rust_script_with_ctrlc_handling(explicit: String, extra_args: Vec<String>) {
725    // let explicit = {
726    //     let lock = EXPLICIT.lock().unwrap_or_else(|e| {
727    //         eprintln!("Failed to acquire lock: {}", e);
728    //         std::process::exit(1); // Exit the program if the lock cannot be obtained
729    //     });
730    //     lock.clone() // Clone the data to move it into the thread
731    // };
732
733    let explicit_path = Path::new(&explicit); // Construct Path outside the lock
734
735    if explicit_path.exists() {
736        is_active_scriptisto(&explicit_path).ok();
737
738        // let extra_args = EXTRA_ARGS.lock().unwrap(); // Locking the Mutex to access the data
739        let extra_str_slice: Vec<String> = extra_args.iter().cloned().collect();
740
741        if is_active_rust_script(&explicit_path).is_ok() {
742            // Run the child process in a separate thread to allow Ctrl+C handling
743            let handle = thread::spawn(move || {
744                let extra_str_slice_cloned = extra_str_slice.clone();
745                let mut child = run_rust_script(
746                    &explicit,
747                    &extra_str_slice_cloned
748                        .iter()
749                        .map(String::as_str)
750                        .collect::<Vec<_>>(),
751                )
752                .unwrap_or_else(|| {
753                    eprintln!("Failed to run rust-script: {:?}", &explicit);
754                    std::process::exit(1); // Exit with an error code
755                });
756
757                // // Lock global to store the child process
758                // {
759                //     let mut global = GLOBAL_CHILD.lock().unwrap();
760                //     *global = Some(child);
761                // }
762
763                // // Wait for the child process to complete
764                // let status = {
765                //     let mut global = GLOBAL_CHILD.lock().unwrap();
766                //     if let Some(mut child) = global.take() {
767                child.wait()
768                //     } else {
769                //         // Handle missing child process
770                //         eprintln!("Child process missing");
771                //         std::process::exit(1); // Exit with an error code
772                //     }
773                // };
774
775                // Handle the child process exit status
776                // match status {
777                //     Ok(status) => {
778                //         eprintln!("Child process exited with status code: {:?}", status.code());
779                //         std::process::exit(status.code().unwrap_or(1)); // Exit with the child's status code
780                //     }
781                //     Err(err) => {
782                //         eprintln!("Error waiting for child process: {}", err);
783                //         std::process::exit(1); // Exit with an error code
784                //     }
785                // }
786            });
787
788            // Wait for the thread to complete, but with a timeout
789            // let timeout = Duration::from_secs(10);
790            // match handle.join_timeout(timeout) {
791            match handle.join() {
792                Ok(_) => {
793                    println!("Child process finished successfully.");
794                }
795                Err(_) => {
796                    eprintln!("Child process took too long to finish. Exiting...");
797                    std::process::exit(1); // Exit if the process takes too long
798                }
799            }
800        }
801    }
802}
803
804pub fn run_scriptisto_with_ctrlc_handling(explicit: String, extra_args: Vec<String>) {
805    let relative: String = make_relative(Path::new(&explicit)).unwrap_or_else(|e| {
806        eprintln!("Error computing relative path: {}", e);
807        std::process::exit(1);
808    });
809
810    let explicit_path = Path::new(&relative);
811    if explicit_path.exists() {
812        // let extra_args = EXTRA_ARGS.lock().unwrap(); // Locking the Mutex to access the data
813        let extra_str_slice: Vec<String> = extra_args.iter().cloned().collect();
814
815        if is_active_scriptisto(&explicit_path).is_ok() {
816            // Run the child process in a separate thread to allow Ctrl+C handling
817            let handle = thread::spawn(move || {
818                let extra_str_slice_cloned: Vec<String> = extra_str_slice.clone();
819                let mut child = run_scriptisto(
820                    &relative,
821                    &extra_str_slice_cloned
822                        .iter()
823                        .map(String::as_str)
824                        .collect::<Vec<_>>(),
825                )
826                .unwrap_or_else(|| {
827                    eprintln!("Failed to run rust-script: {:?}", &explicit);
828                    std::process::exit(1); // Exit with an error code
829                });
830
831                // // Lock global to store the child process
832                // {
833                //     let mut global = GLOBAL_CHILD.lock().unwrap();
834                //     *global = Some(child);
835                // }
836
837                // // Wait for the child process to complete
838                // let status = {
839                //     let mut global = GLOBAL_CHILD.lock().unwrap();
840                //     if let Some(mut child) = global.take() {
841                child.wait()
842                //     } else {
843                //         // Handle missing child process
844                //         eprintln!("Child process missing");
845                //         std::process::exit(1); // Exit with an error code
846                //     }
847                // };
848
849                // // Handle the child process exit status
850                // match status {
851                //     Ok(status) => {
852                //         eprintln!("Child process exited with status code: {:?}", status.code());
853                //         std::process::exit(status.code().unwrap_or(1)); // Exit with the child's status code
854                //     }
855                //     Err(err) => {
856                //         eprintln!("Error waiting for child process: {}", err);
857                //         std::process::exit(1); // Exit with an error code
858                //     }
859                // }
860            });
861
862            // Wait for the thread to complete, but with a timeout
863            // let timeout = Duration::from_secs(10);
864            match handle.join() {
865                Ok(_) => {
866                    println!("Child process finished successfully.");
867                }
868                Err(_) => {
869                    eprintln!("Child process took too long to finish. Exiting...");
870                    std::process::exit(1); // Exit if the process takes too long
871                }
872            }
873        }
874    }
875}
876/// Given any path, produce a relative path string starting with `./` (or `.\` on Windows).
877fn make_relative(path: &Path) -> std::io::Result<String> {
878    let cwd = env::current_dir()?;
879    // Try to strip the cwd prefix; if it isn’t under cwd, just use the original path.
880    let rel: PathBuf = match path.strip_prefix(&cwd) {
881        Ok(stripped) => stripped.to_path_buf(),
882        Err(_) => path.to_path_buf(),
883    };
884
885    let mut rel = if rel.components().count() == 0 {
886        // special case: the same directory
887        PathBuf::from(".")
888    } else {
889        rel
890    };
891
892    // Prepend "./" (or ".\") if it doesn’t already start with "." or ".."
893    let first = rel.components().next().unwrap();
894    match first {
895        std::path::Component::CurDir | std::path::Component::ParentDir => {}
896        _ => {
897            rel = PathBuf::from(".").join(rel);
898        }
899    }
900
901    // Convert back to a string with the correct separator
902    let s = rel
903        .to_str()
904        .expect("Relative path should be valid UTF-8")
905        .to_string();
906
907    Ok(s)
908}
909
910// trait JoinTimeout {
911//     fn join_timeout(self, timeout: Duration) -> Result<(), ()>;
912// }
913
914// impl<T> JoinTimeout for thread::JoinHandle<T> {
915//     fn join_timeout(self, timeout: Duration) -> Result<(), ()> {
916//         println!("Waiting for thread to finish...{}", timeout.as_secs());
917//         let _ = thread::sleep(timeout);
918//         match self.join() {
919//             Ok(_) => Ok(()),
920//             Err(_) => Err(()),
921//         }
922//     }
923// }