use crate::builtin::BuiltinKind;
use crate::builtin::resolve::{CommandKind, resolve_command_kind};
use crate::env::ShellEnv;
use crate::error::ShellError;
pub(crate) fn format_type_line(env: &mut ShellEnv, name: &str) -> (String, Option<String>, i32) {
match resolve_command_kind(env, name) {
CommandKind::Alias(val) => {
let escaped = val.replace('\'', r"'\''");
(format!("{} is aliased to '{}'", name, escaped), None, 0)
}
CommandKind::Keyword => (format!("{} is a shell keyword", name), None, 0),
CommandKind::Function => (format!("{} is a function", name), None, 0),
CommandKind::Builtin(BuiltinKind::Special) => {
(format!("{} is a special shell builtin", name), None, 0)
}
CommandKind::Builtin(BuiltinKind::Regular) => {
(format!("{} is a shell builtin", name), None, 0)
}
CommandKind::Builtin(BuiltinKind::NotBuiltin) => {
(
String::new(),
Some(format!("yosh: type: {}: not found", name)),
1,
)
}
CommandKind::External(p) => (format!("{} is {}", name, p.to_string_lossy()), None, 0),
CommandKind::NotFound => (
String::new(),
Some(format!("yosh: type: {}: not found", name)),
1,
),
}
}
pub fn builtin_type(args: &[String], env: &mut ShellEnv) -> Result<i32, ShellError> {
if args.is_empty() {
eprintln!("yosh: type: usage: type name...");
return Ok(2);
}
let mut exit_status = 0;
for name in args {
let (stdout_line, stderr_line, per_exit) = format_type_line(env, name);
if !stdout_line.is_empty() {
println!("{}", stdout_line);
}
if let Some(s) = stderr_line {
eprintln!("{}", s);
}
if per_exit != 0 {
exit_status = per_exit;
}
}
Ok(exit_status)
}
#[cfg(test)]
mod tests {
use super::*;
fn env_with_path(path: &str) -> ShellEnv {
let mut env = ShellEnv::new("yosh", vec![]);
let _ = env.vars.set("PATH", path);
env
}
#[test]
fn alias_line() {
let mut env = env_with_path("/bin:/usr/bin");
env.aliases.set("ll", "ls -l");
let (out, err, ex) = format_type_line(&mut env, "ll");
assert_eq!(out, "ll is aliased to 'ls -l'");
assert!(err.is_none());
assert_eq!(ex, 0);
}
#[test]
fn alias_single_quote_escaping() {
let mut env = env_with_path("/bin:/usr/bin");
env.aliases.set("q", "echo 'hi'");
let (out, _, _) = format_type_line(&mut env, "q");
assert_eq!(out, r"q is aliased to 'echo '\''hi'\'''");
}
#[test]
fn keyword_line() {
let mut env = env_with_path("/bin:/usr/bin");
let (out, _, ex) = format_type_line(&mut env, "if");
assert_eq!(out, "if is a shell keyword");
assert_eq!(ex, 0);
}
#[test]
fn function_line() {
use crate::parser::ast::{CompoundCommand, CompoundCommandKind, FunctionDef};
use std::rc::Rc;
let mut env = env_with_path("/bin:/usr/bin");
env.functions.insert(
"myfn".to_string(),
FunctionDef {
name: "myfn".to_string(),
body: Rc::new(CompoundCommand {
kind: CompoundCommandKind::BraceGroup { body: Vec::new() },
line: 0,
assignments: Vec::new(),
}),
redirects: Vec::new(),
},
);
let (out, _, ex) = format_type_line(&mut env, "myfn");
assert_eq!(out, "myfn is a function");
assert_eq!(ex, 0);
}
#[test]
fn special_builtin_line() {
let mut env = env_with_path("/bin:/usr/bin");
let (out, _, _) = format_type_line(&mut env, "export");
assert_eq!(out, "export is a special shell builtin");
}
#[test]
fn regular_builtin_line() {
let mut env = env_with_path("/bin:/usr/bin");
let (out, _, _) = format_type_line(&mut env, "cd");
assert_eq!(out, "cd is a shell builtin");
}
#[test]
fn external_line() {
let mut env = env_with_path("/bin:/usr/bin");
let (out, _, ex) = format_type_line(&mut env, "sh");
assert!(out.starts_with("sh is "));
assert!(out.contains("sh"));
assert_eq!(ex, 0);
}
#[test]
fn not_found_line() {
let mut env = env_with_path("/bin:/usr/bin");
let (out, err, ex) = format_type_line(&mut env, "definitely_no_such_cmd_12345");
assert_eq!(out, "");
assert_eq!(
err.unwrap(),
"yosh: type: definitely_no_such_cmd_12345: not found"
);
assert_eq!(ex, 1);
}
#[test]
fn builtin_type_no_args_returns_usage_error() {
let mut env = env_with_path("/bin:/usr/bin");
let r = builtin_type(&[], &mut env).unwrap();
assert_eq!(r, 2);
}
#[test]
fn builtin_type_multi_operand_mixed_success_and_not_found() {
let mut env = env_with_path("/bin:/usr/bin");
let args = vec!["cd".to_string(), "definitely_no_such_cmd_xyz".to_string()];
let r = builtin_type(&args, &mut env).unwrap();
assert_eq!(r, 1);
}
#[test]
fn builtin_type_all_found_returns_zero() {
let mut env = env_with_path("/bin:/usr/bin");
let args = vec!["cd".to_string(), "export".to_string()];
let r = builtin_type(&args, &mut env).unwrap();
assert_eq!(r, 0);
}
}