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}