cargo_e/
e_runner.rs

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