use super::*;
use std::fmt::Write;
use yash_env::function::Function;
use yash_env::function::FunctionSet;
impl PrintFunctions {
pub fn execute(
self,
functions: &FunctionSet,
context: &PrintContext,
) -> Result<String, Vec<ExecuteError>> {
let mut output = String::new();
let mut errors = Vec::new();
if self.functions.is_empty() {
let mut functions = functions.iter().map(AsRef::as_ref).collect::<Vec<_>>();
functions.sort_unstable_by_key(|function| &function.name);
for function in functions {
print_one(function, &self.attrs, context, &mut output);
}
} else {
for name in self.functions {
match functions.get(&name.value) {
Some(function) => print_one(function, &self.attrs, context, &mut output),
None => errors.push(ExecuteError::PrintUnsetFunction(name)),
}
}
}
if errors.is_empty() {
Ok(output)
} else {
Err(errors)
}
}
}
fn print_one(
function: &Function,
filter_attrs: &[(FunctionAttr, State)],
context: &PrintContext,
output: &mut String,
) {
if filter_attrs
.iter()
.any(|&(attr, state)| attr.test(function) != state)
{
return;
}
let name = yash_quote::quoted(&function.name);
if name.needs_quoting() {
output.push_str("function ");
}
writeln!(output, "{}() {}", name, function.body).unwrap();
let mut options_to_print = String::new();
for option in context.options_allowed {
if let Some(attr) = option.attr {
if let Ok(attr) = FunctionAttr::try_from(attr) {
if attr.test(function).into() {
options_to_print.push(option.short);
}
}
}
}
if !options_to_print.is_empty() || context.builtin_is_significant {
writeln!(
output,
"{} -f{} {}",
context.builtin_name, options_to_print, name
)
.unwrap();
}
}
#[cfg(test)]
mod tests {
use super::*;
use yash_env::function::Function;
use yash_env::option::State::{Off, On};
use yash_syntax::syntax::FullCompoundCommand;
#[test]
fn printing_one_function() {
let mut functions = FunctionSet::new();
let foo = Function::new(
"foo",
"{ echo; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("foo location"),
);
let bar = Function::new(
"bar",
"{ ls; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("bar location"),
);
functions.define(foo).unwrap();
functions.define(bar).unwrap();
let pf = PrintFunctions {
functions: Field::dummies(["foo"]),
attrs: vec![],
};
let result = pf.execute(&functions, &PRINT_CONTEXT).unwrap();
assert_eq!(result, "foo() { echo; }\n");
}
#[test]
fn printing_multiple_functions() {
let mut functions = FunctionSet::new();
for i in 1..=4 {
functions
.define(Function::new(
format!("foo{i}"),
format!("{{ echo {i}; }}")
.parse::<FullCompoundCommand>()
.unwrap(),
Location::dummy("foo location"),
))
.unwrap();
}
let pf = PrintFunctions {
functions: Field::dummies(["foo1", "foo2", "foo3"]),
attrs: vec![],
};
assert_eq!(
pf.execute(&functions, &PRINT_CONTEXT).unwrap(),
"foo1() { echo 1; }\nfoo2() { echo 2; }\nfoo3() { echo 3; }\n",
);
}
#[test]
fn printing_all_functions() {
let mut functions = FunctionSet::new();
for i in [2, 4, 3, 1] {
functions
.define(Function::new(
format!("foo{i}"),
format!("{{ echo {i}; }}")
.parse::<FullCompoundCommand>()
.unwrap(),
Location::dummy("foo location"),
))
.unwrap();
}
let pf = PrintFunctions {
functions: vec![],
attrs: vec![],
};
assert_eq!(
pf.execute(&functions, &PRINT_CONTEXT).unwrap(),
"foo1() { echo 1; }\nfoo2() { echo 2; }\nfoo3() { echo 3; }\nfoo4() { echo 4; }\n",
);
}
#[test]
fn quoting_function_name() {
let mut functions = FunctionSet::new();
let function = Function::new(
"a/b$",
"{ echo; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("location"),
);
functions.define(function).unwrap();
let pf = PrintFunctions {
functions: Field::dummies(["a/b$"]),
attrs: vec![],
};
let result = pf.execute(&functions, &PRINT_CONTEXT).unwrap();
assert_eq!(result, "function 'a/b$'() { echo; }\n");
}
#[test]
fn printing_readonly_functions() {
let mut functions = FunctionSet::new();
let foo = Function::new(
"foo",
"{ echo; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("definition location"),
)
.make_read_only(Location::dummy("readonly location"));
functions.define(foo).unwrap();
let pf = PrintFunctions {
functions: Field::dummies(["foo"]),
attrs: vec![],
};
let result = pf.execute(&functions, &PRINT_CONTEXT).unwrap();
assert_eq!(result, "foo() { echo; }\ntypeset -fr foo\n");
}
#[test]
fn selecting_readonly_functions() {
let mut functions = FunctionSet::new();
let foo = Function::new(
"foo",
"{ echo; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("foo location"),
);
let bar = Function::new(
"bar",
"{ ls; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("bar location"),
)
.make_read_only(Location::dummy("bar readonly location"));
functions.define(foo).unwrap();
functions.define(bar).unwrap();
let pf = PrintFunctions {
functions: vec![],
attrs: vec![(FunctionAttr::ReadOnly, On)],
};
let result = pf.execute(&functions, &PRINT_CONTEXT).unwrap();
assert_eq!(result, "bar() { ls; }\ntypeset -fr bar\n");
}
#[test]
fn selecting_non_readonly_functions() {
let mut functions = FunctionSet::new();
let foo = Function::new(
"foo",
"{ echo; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("foo location"),
);
let bar = Function::new(
"bar",
"{ ls; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("bar location"),
)
.make_read_only(Location::dummy("bar readonly location"));
functions.define(foo).unwrap();
functions.define(bar).unwrap();
let pf = PrintFunctions {
functions: vec![],
attrs: vec![(FunctionAttr::ReadOnly, Off)],
};
let result = pf.execute(&functions, &PRINT_CONTEXT).unwrap();
assert_eq!(result, "foo() { echo; }\n");
}
#[test]
fn function_not_found() {
let foo = Field::dummy("foo");
let bar = Field::dummy("bar");
let pf = PrintFunctions {
functions: vec![foo.clone(), bar.clone()],
attrs: vec![],
};
assert_eq!(
pf.execute(&FunctionSet::new(), &PRINT_CONTEXT).unwrap_err(),
[
ExecuteError::PrintUnsetFunction(foo),
ExecuteError::PrintUnsetFunction(bar),
],
);
}
mod non_default_context {
use super::*;
use crate::typeset::syntax::READONLY_OPTION;
#[test]
fn builtin_name() {
let mut functions = FunctionSet::new();
let foo = Function::new(
"foo",
"{ echo; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("definition location"),
)
.make_read_only(Location::dummy("readonly location"));
functions.define(foo).unwrap();
let pf = PrintFunctions {
functions: Field::dummies(["foo"]),
attrs: vec![],
};
let context = PrintContext {
builtin_name: "readonly",
..PRINT_CONTEXT
};
let result = pf.execute(&functions, &context).unwrap();
assert_eq!(result, "foo() { echo; }\nreadonly -fr foo\n");
}
#[test]
fn builtin_is_significant() {
let mut functions = FunctionSet::new();
let foo = Function::new(
"foo",
"{ echo; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("definition location"),
);
functions.define(foo).unwrap();
let pf = PrintFunctions {
functions: Field::dummies(["foo"]),
attrs: vec![],
};
let context = PrintContext {
builtin_is_significant: false,
..PRINT_CONTEXT
};
let result = pf.clone().execute(&functions, &context).unwrap();
assert_eq!(result, "foo() { echo; }\n");
let context = PrintContext {
builtin_is_significant: true,
..PRINT_CONTEXT
};
let result = pf.execute(&functions, &context).unwrap();
assert_eq!(result, "foo() { echo; }\ntypeset -f foo\n");
}
#[test]
fn insignificant_builtin_with_attributed_function() {
let mut functions = FunctionSet::new();
let foo = Function::new(
"foo",
"{ echo; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("definition location"),
)
.make_read_only(Location::dummy("readonly location"));
functions.define(foo).unwrap();
let pf = PrintFunctions {
functions: Field::dummies(["foo"]),
attrs: vec![],
};
let context = PrintContext {
builtin_is_significant: false,
..PRINT_CONTEXT
};
let result = pf.clone().execute(&functions, &context).unwrap();
assert_eq!(result, "foo() { echo; }\ntypeset -fr foo\n");
}
#[test]
fn options_allowed() {
let mut functions = FunctionSet::new();
let foo = Function::new(
"foo",
"{ echo; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy("definition location"),
)
.make_read_only(Location::dummy("readonly location"));
functions.define(foo).unwrap();
let pf = PrintFunctions {
functions: Field::dummies(["foo"]),
attrs: vec![],
};
let context = PrintContext {
options_allowed: &[],
..PRINT_CONTEXT
};
let result = pf.clone().execute(&functions, &context).unwrap();
assert_eq!(result, "foo() { echo; }\n");
let context = PrintContext {
options_allowed: &[READONLY_OPTION],
..PRINT_CONTEXT
};
let result = pf.execute(&functions, &context).unwrap();
assert_eq!(result, "foo() { echo; }\ntypeset -fr foo\n");
}
}
}