use super::identify::NotFound;
use super::search::SearchEnv;
use super::Invoke;
use crate::common::report_failure;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Field;
use yash_env::Env;
use yash_semantics::command::simple_command::execute_function_body;
use yash_semantics::command::simple_command::start_external_utility_in_subshell_and_wait;
use yash_semantics::command_search::search;
use yash_semantics::command_search::Target;
impl Invoke {
pub async fn execute(self, env: &mut Env) -> crate::Result {
let Some(name) = self.fields.first() else {
return crate::Result::default();
};
let params = &self.search;
let search_env = &mut SearchEnv { env, params };
let Some(target) = search(search_env, &name.value) else {
let mut result = report_failure(env, &NotFound { name }).await;
result.set_exit_status(ExitStatus::NOT_FOUND);
return result;
};
invoke_target(env, target, self.fields).await
}
}
async fn invoke_target(env: &mut Env, target: Target, mut fields: Vec<Field>) -> crate::Result {
match target {
Target::Builtin { builtin, .. } => {
let frame = yash_env::stack::Builtin {
name: fields.remove(0),
is_special: false,
};
let mut env = env.push_frame(frame.into());
(builtin.execute)(&mut env, fields).await
}
Target::Function(function) => {
let divert = execute_function_body(env, function, fields, |_| ()).await;
crate::Result::with_exit_status_and_divert(env.exit_status, divert)
}
Target::External { path } => {
let exit_status = start_external_utility_in_subshell_and_wait(env, path, fields).await;
crate::Result::from(exit_status)
}
}
}
#[cfg(test)]
mod tests {
use super::super::Search;
use super::*;
use crate::tests::assert_stderr;
use crate::tests::assert_stdout;
use assert_matches::assert_matches;
use enumset::EnumSet;
use futures_util::FutureExt as _;
use std::ops::ControlFlow::Break;
use std::rc::Rc;
use yash_env::builtin::Builtin;
use yash_env::builtin::Type::Special;
use yash_env::function::Function;
use yash_env::semantics::Field;
use yash_env::VirtualSystem;
use yash_semantics::Divert::Return;
use yash_syntax::source::Location;
use yash_syntax::syntax::FullCompoundCommand;
#[test]
fn empty_command_invocation() {
let mut env = Env::new_virtual();
let invoke = Invoke::default();
let result = invoke.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, crate::Result::default());
}
#[test]
fn command_not_found() {
let system = Box::new(VirtualSystem::new());
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
env.builtins.insert(
"foo",
Builtin {
r#type: Special,
execute: |_, _| unreachable!(),
},
);
let invoke = Invoke {
fields: Field::dummies(["foo"]),
search: Search {
standard_path: false,
categories: EnumSet::empty(),
},
};
let result = invoke.execute(&mut env).now_or_never().unwrap();
assert_eq!(result.exit_status(), ExitStatus::NOT_FOUND);
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
assert_stderr(&state, |stderr| {
assert!(stderr.contains("not found"), "stderr: {stderr:?}");
});
}
#[test]
fn invoking_builtin() {
fn make_result() -> yash_env::builtin::Result {
let mut result = crate::Result::default();
result.set_exit_status(ExitStatus(79));
result.set_divert(Break(Return(None)));
result.retain_redirs();
result
}
let mut env = Env::new_virtual();
let target = Target::Builtin {
builtin: Builtin {
r#type: Special,
execute: |_, args| {
Box::pin(async move {
assert_eq!(args, Field::dummies(["bar", "baz"]));
make_result()
})
},
},
path: None,
};
let result = invoke_target(&mut env, target, Field::dummies(["foo", "bar", "baz"]))
.now_or_never()
.unwrap();
assert_eq!(result, make_result());
}
#[test]
fn invoking_function() {
let mut env = Env::new_virtual();
env.builtins.insert(
":",
Builtin {
r#type: Special,
execute: |_, args| {
Box::pin(async move {
assert_matches!(args.as_slice(), [bar, baz] => {
assert_eq!(bar.value, "bar");
assert_eq!(baz.value, "baz");
});
crate::Result::with_exit_status_and_divert(
ExitStatus(42),
Break(Return(None)),
)
})
},
},
);
let body: FullCompoundCommand = "{ : \"$@\"; }".parse().unwrap();
let origin = Location::dummy("some location");
let target = Target::Function(Rc::new(Function::new("foo", body, origin)));
let result = invoke_target(&mut env, target, Field::dummies(["foo", "bar", "baz"]))
.now_or_never()
.unwrap();
assert_eq!(result, crate::Result::from(ExitStatus(42)));
}
}