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// }