use crate::resolver::resolve_file; use crate::{verify, RunScriptArgs}; use rust_util::util_cmd; use rust_util::util_env::is_env_on; use std::fs; use std::process::Command; const RUNTIME_DENO: &str = "--runtime-deno"; const RUNTIME_BUN: &str = "--runtime-bun"; pub fn do_run_script(args: &RunScriptArgs) { if args.arguments.is_empty() { failure_and_exit!("Must assign a script file name"); } debugging!("Run ts args: {:?}", args.arguments); let mut is_runtime_deno = true; let (raw_script_file, first_arg) = (|| { for (i, arg) in args.arguments.iter().enumerate() { if arg == RUNTIME_DENO || arg == RUNTIME_BUN { is_runtime_deno = arg == RUNTIME_DENO; } if !arg.starts_with("--") { return (arg, i == 0); } } (&args.arguments[args.arguments.len() - 1], false) })(); let script_file = resolve_file(raw_script_file, args.force_update) .unwrap_or_else(|e| failure_and_exit!("Failed to resolve script: {}", e)); let script_file = &script_file; verify::verify_script(script_file, is_env_on("RUNTS_SKIP_VERIFY") || is_env_on("RSV")); let mut cmd = Command::new("/usr/bin/env"); // #!/usr/bin/env runts -- --allow-env // #!/usr/bin/env runts -- [--runtime-deno | --runtime-bun] if first_arg { let first_line_args = read_first_line_parse_args(script_file); debugging!("Runts first line args: {:?}", first_line_args); let left_first_line_args = first_line_args .iter() .skip_while(|arg| *arg != "--" && *arg != "run") .skip(1) .filter(|arg| { if *arg == RUNTIME_DENO || *arg == RUNTIME_BUN { is_runtime_deno = *arg == RUNTIME_DENO; debugging!( "Runts runtime arg: {}, is runtime deno: {}", *arg, is_runtime_deno ); return false; } true }) .collect::>(); cmd.args(["-S", iff!(is_runtime_deno, "deno", "bun"), "run"]); cmd.args(left_first_line_args); } else { cmd.args(["-S", iff!(is_runtime_deno, "deno", "bun"), "run"]); } for arg in &args.arguments { if arg == RUNTIME_DENO || arg == RUNTIME_BUN { continue; } if arg == raw_script_file { // replace remote file with local file cmd.arg(script_file); } else { cmd.arg(arg); } } debugging!("Run command: {cmd:?}"); if let Err(e) = util_cmd::run_command_and_wait(&mut cmd) { failure_and_exit!("Run deno: {script_file} failed: {e}"); } } fn read_first_line_parse_args(script_file: &str) -> Vec { if let Ok(script_content) = fs::read_to_string(script_file) { if let Some(first_line) = script_content.lines().next() { if first_line.starts_with("#!") { return parse_args_line(&first_line); } } } vec![] } fn parse_args_line(line: &str) -> Vec { let mut args = vec![]; let mut ln = String::new(); let mut in_quota = false; let mut single_quota = false; let chars = line.chars().collect::>(); let mut i = 0; while i < chars.len() { let c = chars[i]; if in_quota { if (single_quota && c == '\'') || (!single_quota && c == '"') { in_quota = false; } else if c == '\\' { if i + 1 < chars.len() { i += 1; let nc = chars[i]; ln.push(nc); } else { ln.push(c); } } else { ln.push(c); } } else if c == ' ' || c == '\t' { if !ln.is_empty() { args.push(ln.clone()); ln.clear(); } } else if c == '\'' { in_quota = true; single_quota = true; } else if c == '"' { in_quota = true; single_quota = false; } else { ln.push(c); } i += 1; } if !ln.is_empty() { args.push(ln); } args } #[test] fn test_parse_args_line() { assert!(parse_args_line("").is_empty()); assert_eq!(vec!["a".to_string()], parse_args_line("a")); assert_eq!( vec!["a".to_string(), "aaaa'bbb".to_string()], parse_args_line("a 'aaaa\\'bbb'") ); assert_eq!( vec!["--allow-a".to_string(), "--allow-b".to_string()], parse_args_line(" --allow-a --allow-b") ); }