use std::io::Write;
use crate::parser::ShellCommand;
use crate::state::ShellState;
pub struct ReturnBuiltin;
impl super::Builtin for ReturnBuiltin {
fn name(&self) -> &'static str {
"return"
}
fn names(&self) -> Vec<&'static str> {
vec![self.name()]
}
fn description(&self) -> &'static str {
"Return from a shell function"
}
fn run(
&self,
cmd: &ShellCommand,
shell_state: &mut ShellState,
_output_writer: &mut dyn Write,
) -> i32 {
if shell_state.function_depth == 0 {
if shell_state.colors_enabled {
eprintln!(
"{}return: can only be used in a function\x1b[0m",
shell_state.color_scheme.error
);
} else {
eprintln!("return: can only be used in a function");
}
return 1;
}
let exit_code = if cmd.args.len() > 1 {
match cmd.args[1].parse::<i32>() {
Ok(code) => code,
Err(_) => {
if shell_state.colors_enabled {
eprintln!(
"{}return: {}: numeric argument required\x1b[0m",
shell_state.color_scheme.error, cmd.args[1]
);
} else {
eprintln!("return: {}: numeric argument required", cmd.args[1]);
}
return 2;
}
}
} else {
shell_state.last_exit_code
};
shell_state.set_return(exit_code);
exit_code
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builtins::Builtin;
use std::sync::Mutex;
static ENV_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn test_return_builtin_basic() {
let _lock = ENV_LOCK.lock().unwrap();
let cmd = ShellCommand {
args: vec!["return".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
shell_state.last_exit_code = 42;
shell_state.enter_function();
let builtin = ReturnBuiltin;
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 42); assert!(shell_state.is_returning());
assert_eq!(shell_state.get_return_value(), Some(42));
shell_state.exit_function();
}
#[test]
fn test_return_builtin_with_explicit_code() {
let _lock = ENV_LOCK.lock().unwrap();
let cmd = ShellCommand {
args: vec!["return".to_string(), "5".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
shell_state.enter_function();
let builtin = ReturnBuiltin;
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 5);
assert!(shell_state.is_returning());
assert_eq!(shell_state.get_return_value(), Some(5));
shell_state.exit_function();
}
#[test]
fn test_return_builtin_invalid_argument() {
let _lock = ENV_LOCK.lock().unwrap();
let cmd = ShellCommand {
args: vec!["return".to_string(), "invalid".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
shell_state.enter_function();
let builtin = ReturnBuiltin;
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 2); assert!(!shell_state.is_returning());
shell_state.exit_function();
}
#[test]
fn test_return_builtin_outside_function() {
let _lock = ENV_LOCK.lock().unwrap();
let cmd = ShellCommand {
args: vec!["return".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
assert_eq!(shell_state.function_depth, 0);
let builtin = ReturnBuiltin;
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); assert!(!shell_state.is_returning());
}
#[test]
fn test_return_builtin_nested_functions() {
let _lock = ENV_LOCK.lock().unwrap();
let cmd = ShellCommand {
args: vec!["return".to_string(), "10".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
shell_state.enter_function(); shell_state.enter_function();
assert_eq!(shell_state.function_depth, 2);
let builtin = ReturnBuiltin;
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 10);
assert!(shell_state.is_returning());
assert_eq!(shell_state.get_return_value(), Some(10));
shell_state.exit_function();
shell_state.exit_function();
}
#[test]
fn test_return_builtin_value_propagation() {
let _lock = ENV_LOCK.lock().unwrap();
let mut shell_state = ShellState::new();
shell_state.enter_function();
let cmd = ShellCommand {
args: vec!["return".to_string(), "123".to_string()],
redirections: Vec::new(),
compound: None,
};
let builtin = ReturnBuiltin;
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 123);
assert!(shell_state.is_returning());
assert_eq!(shell_state.get_return_value(), Some(123));
shell_state.clear_return();
assert!(!shell_state.is_returning());
assert_eq!(shell_state.get_return_value(), None);
shell_state.exit_function();
}
#[test]
fn test_return_builtin_zero_code() {
let _lock = ENV_LOCK.lock().unwrap();
let cmd = ShellCommand {
args: vec!["return".to_string(), "0".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
shell_state.enter_function();
let builtin = ReturnBuiltin;
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0);
assert!(shell_state.is_returning());
assert_eq!(shell_state.get_return_value(), Some(0));
shell_state.exit_function();
}
#[test]
fn test_return_builtin_negative_code() {
let _lock = ENV_LOCK.lock().unwrap();
let cmd = ShellCommand {
args: vec!["return".to_string(), "-1".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
shell_state.enter_function();
let builtin = ReturnBuiltin;
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, -1);
assert!(shell_state.is_returning());
assert_eq!(shell_state.get_return_value(), Some(-1));
shell_state.exit_function();
}
}