use super::context::ImmutableEnvironmentContext;
use crate::cook::orchestrator::ExecutionEnvironment;
use crate::cook::workflow::WorkflowStep;
use std::collections::HashMap;
use std::path::PathBuf;
pub fn resolve_working_directory(
step: &WorkflowStep,
_env: &ExecutionEnvironment,
context: &ImmutableEnvironmentContext,
) -> PathBuf {
if let Some(ref dir) = step.working_dir {
return dir.clone();
}
context.working_dir().to_path_buf()
}
pub fn build_command_env(
step: &WorkflowStep,
context: &ImmutableEnvironmentContext,
workflow_vars: &HashMap<String, String>,
) -> HashMap<String, String> {
let mut env = context.env_vars().clone();
for (key, value) in &step.env {
let interpolated = interpolate_value(value, workflow_vars);
env.insert(key.clone(), interpolated);
}
env.insert("PRODIGY_AUTOMATION".to_string(), "true".to_string());
env
}
pub fn inject_positional_args(env_vars: &mut HashMap<String, String>, args: &[String]) {
for (index, arg) in args.iter().enumerate() {
let var_name = format!("ARG_{}", index + 1);
env_vars.insert(var_name, arg.clone());
}
}
pub fn interpolate_value(value: &str, variables: &HashMap<String, String>) -> String {
let mut result = value.to_string();
for (key, val) in variables {
result = result.replace(&format!("${{{}}}", key), val);
result = result.replace(&format!("${}", key), val);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn create_test_env() -> ExecutionEnvironment {
ExecutionEnvironment {
working_dir: Arc::new(PathBuf::from("/env")),
project_dir: Arc::new(PathBuf::from("/project")),
worktree_name: None,
session_id: Arc::from("test"),
}
}
#[test]
fn test_resolve_working_directory_explicit_step() {
let step = WorkflowStep {
working_dir: Some(PathBuf::from("/explicit")),
..Default::default()
};
let env = create_test_env();
let context = ImmutableEnvironmentContext::new(PathBuf::from("/context"));
let result = resolve_working_directory(&step, &env, &context);
assert_eq!(result, PathBuf::from("/explicit"));
}
#[test]
fn test_resolve_working_directory_from_context() {
let step = WorkflowStep {
working_dir: None,
..Default::default()
};
let env = create_test_env();
let context = ImmutableEnvironmentContext::new(PathBuf::from("/worktree"));
let result = resolve_working_directory(&step, &env, &context);
assert_eq!(result, PathBuf::from("/worktree"));
}
#[test]
fn test_resolve_working_directory_is_pure() {
let step = WorkflowStep {
working_dir: None,
..Default::default()
};
let env = create_test_env();
let context = ImmutableEnvironmentContext::new(PathBuf::from("/test"));
let result1 = resolve_working_directory(&step, &env, &context);
let result2 = resolve_working_directory(&step, &env, &context);
let result3 = resolve_working_directory(&step, &env, &context);
assert_eq!(result1, result2);
assert_eq!(result2, result3);
}
#[test]
fn test_build_command_env_step_vars() {
let step = WorkflowStep {
env: vec![("CUSTOM".to_string(), "value".to_string())]
.into_iter()
.collect(),
..Default::default()
};
let context = ImmutableEnvironmentContext::new(PathBuf::from("/test"));
let workflow_vars = HashMap::new();
let result = build_command_env(&step, &context, &workflow_vars);
assert_eq!(result.get("CUSTOM"), Some(&"value".to_string()));
assert_eq!(result.get("PRODIGY_AUTOMATION"), Some(&"true".to_string()));
}
#[test]
fn test_build_command_env_interpolation() {
let step = WorkflowStep {
env: vec![("MESSAGE".to_string(), "Hello ${NAME}".to_string())]
.into_iter()
.collect(),
..Default::default()
};
let context = ImmutableEnvironmentContext::new(PathBuf::from("/test"));
let mut workflow_vars = HashMap::new();
workflow_vars.insert("NAME".to_string(), "World".to_string());
let result = build_command_env(&step, &context, &workflow_vars);
assert_eq!(result.get("MESSAGE"), Some(&"Hello World".to_string()));
}
#[test]
fn test_build_command_env_inherits_context_vars() {
let step = WorkflowStep {
env: HashMap::new(),
..Default::default()
};
let mut env_vars = HashMap::new();
env_vars.insert("FROM_CONTEXT".to_string(), "context_value".to_string());
let context = ImmutableEnvironmentContext {
base_working_dir: Arc::new(PathBuf::from("/test")),
env_vars: Arc::new(env_vars),
secret_keys: Arc::new(Vec::new()),
profile: None,
};
let workflow_vars = HashMap::new();
let result = build_command_env(&step, &context, &workflow_vars);
assert_eq!(
result.get("FROM_CONTEXT"),
Some(&"context_value".to_string())
);
assert_eq!(result.get("PRODIGY_AUTOMATION"), Some(&"true".to_string()));
}
#[test]
fn test_interpolate_value_bracketed() {
let mut variables = HashMap::new();
variables.insert("VAR".to_string(), "value".to_string());
let result = interpolate_value("prefix-${VAR}-suffix", &variables);
assert_eq!(result, "prefix-value-suffix");
}
#[test]
fn test_interpolate_value_simple() {
let mut variables = HashMap::new();
variables.insert("VAR".to_string(), "value".to_string());
let result = interpolate_value("prefix-$VAR-suffix", &variables);
assert_eq!(result, "prefix-value-suffix");
}
#[test]
fn test_interpolate_value_multiple() {
let mut variables = HashMap::new();
variables.insert("A".to_string(), "1".to_string());
variables.insert("B".to_string(), "2".to_string());
let result = interpolate_value("${A} and ${B} and $A and $B", &variables);
assert_eq!(result, "1 and 2 and 1 and 2");
}
#[test]
fn test_interpolate_value_no_variables() {
let variables = HashMap::new();
let result = interpolate_value("no variables here", &variables);
assert_eq!(result, "no variables here");
}
#[test]
fn test_interpolate_value_missing_variable() {
let variables = HashMap::new();
let result = interpolate_value("missing ${VAR} here", &variables);
assert_eq!(result, "missing ${VAR} here");
}
#[test]
fn test_build_command_env_is_pure() {
let step = WorkflowStep {
env: vec![("KEY".to_string(), "value".to_string())]
.into_iter()
.collect(),
..Default::default()
};
let context = ImmutableEnvironmentContext::new(PathBuf::from("/test"));
let workflow_vars = HashMap::new();
let result1 = build_command_env(&step, &context, &workflow_vars);
let result2 = build_command_env(&step, &context, &workflow_vars);
assert_eq!(result1, result2);
}
#[test]
fn test_inject_positional_args() {
let mut env = HashMap::new();
let args = vec!["file.txt".to_string(), "output.json".to_string()];
inject_positional_args(&mut env, &args);
assert_eq!(env.get("ARG_1"), Some(&"file.txt".to_string()));
assert_eq!(env.get("ARG_2"), Some(&"output.json".to_string()));
}
#[test]
fn test_inject_positional_args_empty() {
let mut env = HashMap::new();
let args: Vec<String> = vec![];
inject_positional_args(&mut env, &args);
assert!(env.is_empty());
}
#[test]
fn test_inject_positional_args_with_special_chars() {
let mut env = HashMap::new();
let args = vec!["path/with spaces/file.md".to_string()];
inject_positional_args(&mut env, &args);
assert_eq!(
env.get("ARG_1"),
Some(&"path/with spaces/file.md".to_string())
);
}
#[test]
fn test_inject_positional_args_preserves_existing() {
let mut env = HashMap::new();
env.insert("EXISTING".to_string(), "value".to_string());
let args = vec!["arg1".to_string()];
inject_positional_args(&mut env, &args);
assert_eq!(env.get("EXISTING"), Some(&"value".to_string()));
assert_eq!(env.get("ARG_1"), Some(&"arg1".to_string()));
}
#[test]
fn test_inject_positional_args_is_pure() {
let args = vec!["test".to_string()];
let mut env1 = HashMap::new();
inject_positional_args(&mut env1, &args);
let mut env2 = HashMap::new();
inject_positional_args(&mut env2, &args);
assert_eq!(env1, env2);
}
}