use crate::config::{Config, DefaultOperation};
use crate::gui::{UserChoice, interactive_prompt};
use crate::log_debug;
use crate::platform::resolve_executable;
use crate::script::ScriptMetadata;
use std::collections::HashMap;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::{fs, io};
pub(crate) fn build_command(
script: &ScriptMetadata,
extra_args: Option<Vec<String>>,
_config: &Config,
) -> Command {
log_debug!("build_command({:?}, {:?})", script, &_config);
let mut command =
Command::new(&script.association.as_ref().unwrap().exec_runtime);
if let Some(arg_string) =
&script.association.as_ref().unwrap().exec_argv_override
{
let mut vars = HashMap::new();
let file_path = script.file_path.to_str().unwrap();
vars.insert("script", file_path.replace("\\", "\\\\"));
vars.insert("script_unix", file_path.replace("\\", "/"));
expand_and_push_args(
&mut command,
arg_string,
&vars,
extra_args.as_ref(),
);
} else {
log_debug!("No exec argv override found, using default behavior");
if let Some(arg) = &script.shebang_arg {
for part in
shell_words::split(arg).unwrap_or_else(|_| vec![arg.clone()])
{
command.arg(part);
}
}
command.arg(&script.file_path);
if let Some(extra_args) = extra_args {
for arg in extra_args {
command.arg(arg);
}
}
}
command
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
command
}
pub(crate) fn handle_interactive_dispatch(
script: &ScriptMetadata,
command: &mut Command,
config: &Config,
) -> io::Result<()> {
log_debug!("Interactive dispatch for script: {:?}", script);
let editor = resolve_view_runtime(script, config);
let operation = resolve_operation(script, config);
log_debug!("Editor resolved: {:?}", editor);
log_debug!("Operation resolved: {:?}", operation);
match operation {
DefaultOperation::Prompt => {
match interactive_prompt(script, &editor)? {
UserChoice::Run => {
let mut child = command.spawn()?;
child.wait()?;
log_debug!(&format!("Script executed: {:?}", script));
}
UserChoice::Edit => { }
UserChoice::Exit => { }
}
}
DefaultOperation::Execute => {
let mut child = command.spawn()?;
child.wait()?;
log_debug!(&format!("Script auto-executed: {:?}", script));
}
DefaultOperation::Open => {
let editor_path = which::which(&editor)
.unwrap_or_else(|_| PathBuf::from("notepad"));
Command::new(editor_path)
.arg::<&PathBuf>(&script.file_path)
.spawn()?
.wait()?;
log_debug!(&format!(
"Script opened in editor: {:?} -> {:?}",
editor, script
));
}
}
Ok(())
}
pub(crate) fn handle_fallback_dispatch(
script: &ScriptMetadata,
config: &Config,
) -> io::Result<()> {
let metadata = fs::metadata(&script.file_path)?;
let size_mb = metadata.len() / 1_048_576;
let (fallback_util, fallback_args) =
if let Some(default_large) = &config.default_large {
if size_mb >= default_large.size_mb_threshold {
(
&default_large.view_runtime,
default_large.args.as_deref().unwrap_or("$script"),
)
} else if let Some(default) = &config.default {
(
&default.view_runtime,
default.args.as_deref().unwrap_or("$script"),
)
} else {
(&"notepad".to_string(), "$script")
}
} else if let Some(default) = &config.default {
(
&default.view_runtime,
default.args.as_deref().unwrap_or("$script"),
)
} else {
(&"notepad".to_string(), "$script")
};
let resolved = which::which(fallback_util)
.unwrap_or_else(|_| PathBuf::from(fallback_util));
let mut fallback_cmd = Command::new(resolved);
if fallback_args.contains("$script") {
for part in shell_words::split(fallback_args).unwrap_or_default() {
if part == "$script" {
fallback_cmd.arg(&script.file_path);
} else {
fallback_cmd.arg(part);
}
}
} else {
for part in shell_words::split(fallback_args).unwrap_or_default() {
fallback_cmd.arg(part);
}
fallback_cmd.arg(&script.file_path);
}
fallback_cmd
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
let mut child = fallback_cmd.spawn()?;
child.wait()?;
Ok(())
}
fn resolve_view_runtime(script: &ScriptMetadata, config: &Config) -> String {
if let Some(runtime) = script
.association
.as_ref()
.and_then(|a| a.view_runtime.clone())
{
return runtime;
}
if let Some(default_large) = &config.default_large {
if script.file_size / 1_048_576 >= default_large.size_mb_threshold {
log_debug!(&format!(
"File size exceeds threshold: {} MB",
script.file_size / 1_048_576
));
return default_large.view_runtime.clone();
} else {
log_debug!(&format!(
"File size is within threshold: {} MB",
script.file_size / 1_048_576
));
}
}
if let Some(default) = &config.default {
return default.view_runtime.clone();
}
resolve_executable("code")
.map(|_| "code".to_string())
.unwrap_or_else(|| "notepad".to_string())
}
fn resolve_operation(
script: &ScriptMetadata,
config: &Config,
) -> DefaultOperation {
if let Some(op) = script
.association
.as_ref()
.and_then(|a| a.default_operation.clone())
{
return op;
}
if let Some(op) = config.default_operation {
return op;
}
DefaultOperation::Prompt
}
fn expand_and_push_args(
command: &mut Command,
arg_str: &str,
vars: &HashMap<&str, String>,
passed_args: Option<&Vec<String>>,
) {
log_debug!(&format!("Expanding arguments with vars: {:?}", vars));
for part in shell_words::split(arg_str).unwrap_or_default() {
log_debug!(&format!("Expanding part: '{}'", part));
if part == "@{passed_args}" {
if let Some(args) = passed_args {
for arg in args {
log_debug!(&format!("Adding passed argument: '{}'", arg));
command.arg(arg);
}
}
continue;
}
let expanded = expand_placeholders(&part, vars);
if expanded.is_empty() {
log_debug!("Skipping empty expanded argument");
continue;
}
log_debug!(&format!("Expanded argument: '{}'", expanded));
command.arg(expanded);
}
}
fn expand_placeholders(s: &str, vars: &HashMap<&str, String>) -> String {
let mut result = s.to_owned();
for (key, val) in vars {
let placeholder = format!("@{{{}}}", key);
result = result.replace(&placeholder, val);
}
result
}