Skip to main content

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}