zsh/compsys/ported/Base/Utility/_call_function.rs
1//! Port of `_call_function` from
2//! `Completion/Base/Utility/_call_function`.
3//!
4//! Full upstream body (32 lines verbatim):
5//! ```text
6//! sh: 1 #autoload
7//! sh: 5 # Usage: _call_function <return> <name> [ <args> ... ]
8//! sh:15 local _name _ret
9//! sh:17 [[ "$1" != (|-) ]] && _name="$1"
10//! sh:19 shift
11//! sh:21 if (( $+functions[$1] )); then
12//! sh:22 "$@"
13//! sh:23 _ret="$?"
14//! sh:25 [[ -n "$_name" ]] && eval "${_name}=${_ret}"
15//! sh:27 compstate[restore]=''
16//! sh:29 return 0
17//! sh:30 fi
18//! sh:32 return 1
19//! ```
20//!
21//! Calls a shell function if it exists, captures its return into
22//! the param named by `$1` (when not empty/`-`), and clears
23//! `$compstate[restore]`. Returns 0 if the fn was called, 1
24//! otherwise.
25
26use crate::ported::exec::dispatch_function_call;
27use crate::ported::params::setsparam;
28use crate::ported::utils::getshfunc;
29use crate::ported::zle::compcore::set_compstate_str;
30
31/// `_call_function` — invoke a named shell function with the rest
32/// of the args, storing its exit status under the param named by
33/// `$1`. Returns 0 if invoked, 1 if the named fn doesn't exist.
34pub fn _call_function(args: &[String]) -> i32 {
35 // sh:17 $1 is the return-storage param name (or `-`/empty = no store)
36 let result_name = args.first().cloned().unwrap_or_default();
37 let store_into: Option<String> = if result_name.is_empty() || result_name == "-" {
38 None
39 } else {
40 Some(result_name)
41 };
42
43 // sh:19 shift
44 let rest: &[String] = if args.is_empty() { &[] } else { &args[1..] };
45
46 // sh:21 test fn existence
47 let fn_name = match rest.first() {
48 Some(n) => n.clone(),
49 None => return 1,
50 };
51 if getshfunc(&fn_name).is_none() {
52 // sh:32
53 return 1;
54 }
55
56 // sh:22-23 invoke and capture exit
57 let call_args: &[String] = if rest.len() > 1 { &rest[1..] } else { &[] };
58 let ret = dispatch_function_call(&fn_name, call_args).unwrap_or(1);
59
60 // sh:25
61 if let Some(name) = store_into {
62 let _ = setsparam(&name, &ret.to_string());
63 }
64
65 // sh:27
66 set_compstate_str("restore", "");
67
68 // sh:29
69 0
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn missing_fn_returns_one() {
78 // sh:32 — the named fn doesn't exist → return 1.
79 let _g = crate::test_util::global_state_lock();
80 assert_eq!(
81 _call_function(&["ret".to_string(), "nonexistent_fn".to_string()]),
82 1
83 );
84 }
85
86 #[test]
87 fn empty_args_returns_one() {
88 let _g = crate::test_util::global_state_lock();
89 assert_eq!(_call_function(&[]), 1);
90 }
91}