1use super::*;
18use std::fmt::Write;
19use yash_env::function::Function;
20use yash_env::function::FunctionSet;
21
22impl PrintFunctions {
23 pub fn execute<S>(
25 self,
26 functions: &FunctionSet<S>,
27 context: &PrintContext,
28 ) -> Result<String, Vec<ExecuteError>> {
29 let mut output = String::new();
30 let mut errors = Vec::new();
31
32 if self.functions.is_empty() {
33 let mut functions = functions.iter().map(AsRef::as_ref).collect::<Vec<_>>();
34 functions.sort_unstable_by_key(|function| &function.name);
36 for function in functions {
37 print_one(function, &self.attrs, context, &mut output);
38 }
39 } else {
40 for name in self.functions {
41 match functions.get(&name.value) {
42 Some(function) => print_one(function, &self.attrs, context, &mut output),
43 None => errors.push(ExecuteError::PrintUnsetFunction(name)),
44 }
45 }
46 }
47
48 if errors.is_empty() {
49 Ok(output)
50 } else {
51 Err(errors)
52 }
53 }
54}
55
56fn print_one<S>(
57 function: &Function<S>,
58 filter_attrs: &[(FunctionAttr, State)],
59 context: &PrintContext,
60 output: &mut String,
61) {
62 if filter_attrs
64 .iter()
65 .any(|&(attr, state)| attr.test(function) != state)
66 {
67 return;
68 }
69
70 let name = yash_quote::quoted(&function.name);
72 if name.needs_quoting() {
73 output.push_str("function ");
74 }
75 writeln!(output, "{}() {}", name, function.body).unwrap();
77
78 let mut options_to_print = String::new();
80 for option in context.options_allowed {
81 if let Some(attr) = option.attr {
82 if let Ok(attr) = FunctionAttr::try_from(attr) {
83 if attr.test(function).into() {
84 options_to_print.push(option.short);
85 }
86 }
87 }
88 }
89 if !options_to_print.is_empty() || context.builtin_is_significant {
90 let separator = if function.name.starts_with('-') {
91 "-- "
92 } else {
93 ""
94 };
95
96 writeln!(
97 output,
98 "{} -f{} {}{}",
99 context.builtin_name, options_to_print, separator, name
100 )
101 .unwrap();
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use std::rc::Rc;
109 use yash_env::Env;
110 use yash_env::function::{FunctionBody, FunctionBodyObject};
111 use yash_env::option::State::{Off, On};
112
113 #[derive(Clone, Debug)]
114 struct FunctionBodyStub(String);
115
116 impl FunctionBodyStub {
117 fn new<S: Into<String>>(s: S) -> Self {
118 FunctionBodyStub(s.into())
119 }
120 }
121 impl std::fmt::Display for FunctionBodyStub {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 self.0.fmt(f)
124 }
125 }
126 impl<S> FunctionBody<S> for FunctionBodyStub {
127 async fn execute(&self, _: &mut Env<S>) -> yash_env::semantics::Result {
128 unreachable!()
129 }
130 }
131
132 fn function_body_stub<S>(src: &str) -> Rc<dyn FunctionBodyObject<S>> {
133 Rc::new(FunctionBodyStub::new(src))
134 }
135
136 #[test]
137 fn printing_one_function() {
138 let mut functions = FunctionSet::<()>::new();
139 let foo = Function::new(
140 "foo",
141 function_body_stub("{ echo; }"),
142 Location::dummy("foo location"),
143 );
144 let bar = Function::new(
145 "bar",
146 function_body_stub("{ ls; }"),
147 Location::dummy("bar location"),
148 );
149 functions.define(foo).unwrap();
150 functions.define(bar).unwrap();
151 let pf = PrintFunctions {
152 functions: Field::dummies(["foo"]),
153 attrs: vec![],
154 };
155
156 let result = pf.execute(&functions, &PRINT_CONTEXT).unwrap();
157 assert_eq!(result, "foo() { echo; }\n");
158 }
159
160 #[test]
161 fn printing_multiple_functions() {
162 let mut functions = FunctionSet::<()>::new();
163 for i in 1..=4 {
164 functions
165 .define(Function::new(
166 format!("foo{i}"),
167 function_body_stub(&format!("{{ echo {i}; }}")),
168 Location::dummy("foo location"),
169 ))
170 .unwrap();
171 }
172 let pf = PrintFunctions {
173 functions: Field::dummies(["foo1", "foo2", "foo3"]),
174 attrs: vec![],
175 };
176
177 assert_eq!(
178 pf.execute(&functions, &PRINT_CONTEXT).unwrap(),
179 "foo1() { echo 1; }\nfoo2() { echo 2; }\nfoo3() { echo 3; }\n",
180 );
181 }
182
183 #[test]
184 fn printing_all_functions() {
185 let mut functions = FunctionSet::<()>::new();
186 for i in [2, 4, 3, 1] {
187 functions
188 .define(Function::new(
189 format!("foo{i}"),
190 function_body_stub(&format!("{{ echo {i}; }}")),
191 Location::dummy("foo location"),
192 ))
193 .unwrap();
194 }
195 let pf = PrintFunctions {
196 functions: vec![],
197 attrs: vec![],
198 };
199
200 assert_eq!(
202 pf.execute(&functions, &PRINT_CONTEXT).unwrap(),
203 "foo1() { echo 1; }\nfoo2() { echo 2; }\nfoo3() { echo 3; }\nfoo4() { echo 4; }\n",
204 );
205 }
206
207 #[test]
208 fn quoting_function_name() {
209 let mut functions = FunctionSet::<()>::new();
210 let function = Function::new(
211 "a/b$",
212 function_body_stub("{ echo; }"),
213 Location::dummy("location"),
214 );
215 functions.define(function).unwrap();
216 let pf = PrintFunctions {
217 functions: Field::dummies(["a/b$"]),
218 attrs: vec![],
219 };
220
221 let result = pf.execute(&functions, &PRINT_CONTEXT).unwrap();
222 assert_eq!(result, "function 'a/b$'() { echo; }\n");
223 }
224
225 #[test]
226 fn printing_readonly_functions() {
227 let mut functions = FunctionSet::<()>::new();
228 let foo = Function::new(
229 "foo",
230 function_body_stub("{ echo; }"),
231 Location::dummy("definition location"),
232 )
233 .make_read_only(Location::dummy("readonly location"));
234 functions.define(foo).unwrap();
235 let pf = PrintFunctions {
236 functions: Field::dummies(["foo"]),
237 attrs: vec![],
238 };
239
240 let result = pf.execute(&functions, &PRINT_CONTEXT).unwrap();
241 assert_eq!(result, "foo() { echo; }\ntypeset -fr foo\n");
242 }
243
244 #[test]
245 fn printing_function_name_starting_with_hyphen() {
246 let mut functions = FunctionSet::<()>::new();
247 let foo = Function::new(
248 "-n",
249 function_body_stub("{ :; }"),
250 Location::dummy("definition location"),
251 )
252 .make_read_only(Location::dummy("readonly location"));
253 functions.define(foo).unwrap();
254 let pf = PrintFunctions {
255 functions: Field::dummies(["-n"]),
256 attrs: vec![],
257 };
258
259 let result = pf.execute(&functions, &PRINT_CONTEXT).unwrap();
260 assert_eq!(result, "-n() { :; }\ntypeset -fr -- -n\n");
261 }
262
263 #[test]
264 fn selecting_readonly_functions() {
265 let mut functions = FunctionSet::<()>::new();
266 let foo = Function::new(
267 "foo",
268 function_body_stub("{ echo; }"),
269 Location::dummy("foo location"),
270 );
271 let bar = Function::new(
272 "bar",
273 function_body_stub("{ ls; }"),
274 Location::dummy("bar location"),
275 )
276 .make_read_only(Location::dummy("bar readonly location"));
277 functions.define(foo).unwrap();
278 functions.define(bar).unwrap();
279 let pf = PrintFunctions {
280 functions: vec![],
281 attrs: vec![(FunctionAttr::ReadOnly, On)],
282 };
283
284 let result = pf.execute(&functions, &PRINT_CONTEXT).unwrap();
285 assert_eq!(result, "bar() { ls; }\ntypeset -fr bar\n");
286 }
287
288 #[test]
289 fn selecting_non_readonly_functions() {
290 let mut functions = FunctionSet::<()>::new();
291 let foo = Function::new(
292 "foo",
293 function_body_stub("{ echo; }"),
294 Location::dummy("foo location"),
295 );
296 let bar = Function::new(
297 "bar",
298 function_body_stub("{ ls; }"),
299 Location::dummy("bar location"),
300 )
301 .make_read_only(Location::dummy("bar readonly location"));
302 functions.define(foo).unwrap();
303 functions.define(bar).unwrap();
304 let pf = PrintFunctions {
305 functions: vec![],
306 attrs: vec![(FunctionAttr::ReadOnly, Off)],
307 };
308
309 let result = pf.execute(&functions, &PRINT_CONTEXT).unwrap();
310 assert_eq!(result, "foo() { echo; }\n");
311 }
312
313 #[test]
314 fn function_not_found() {
315 let foo = Field::dummy("foo");
316 let bar = Field::dummy("bar");
317 let pf = PrintFunctions {
318 functions: vec![foo.clone(), bar.clone()],
319 attrs: vec![],
320 };
321
322 let errors = pf
323 .execute(&FunctionSet::<()>::new(), &PRINT_CONTEXT)
324 .unwrap_err();
325 assert_eq!(
326 errors,
327 [
328 ExecuteError::PrintUnsetFunction(foo),
329 ExecuteError::PrintUnsetFunction(bar),
330 ],
331 );
332 }
333
334 mod non_default_context {
335 use super::*;
336 use crate::typeset::syntax::READONLY_OPTION;
337
338 #[test]
339 fn builtin_name() {
340 let mut functions = FunctionSet::<()>::new();
341 let foo = Function::new(
342 "foo",
343 function_body_stub("{ echo; }"),
344 Location::dummy("definition location"),
345 )
346 .make_read_only(Location::dummy("readonly location"));
347 functions.define(foo).unwrap();
348 let pf = PrintFunctions {
349 functions: Field::dummies(["foo"]),
350 attrs: vec![],
351 };
352 let context = PrintContext {
353 builtin_name: "readonly",
354 ..PRINT_CONTEXT
355 };
356
357 let result = pf.execute(&functions, &context).unwrap();
358 assert_eq!(result, "foo() { echo; }\nreadonly -fr foo\n");
359 }
360
361 #[test]
362 fn builtin_is_significant() {
363 let mut functions = FunctionSet::<()>::new();
364 let foo = Function::new(
365 "foo",
366 function_body_stub("{ echo; }"),
367 Location::dummy("definition location"),
368 );
369 functions.define(foo).unwrap();
370 let pf = PrintFunctions {
371 functions: Field::dummies(["foo"]),
372 attrs: vec![],
373 };
374
375 let context = PrintContext {
376 builtin_is_significant: false,
377 ..PRINT_CONTEXT
378 };
379 let result = pf.clone().execute(&functions, &context).unwrap();
380 assert_eq!(result, "foo() { echo; }\n");
381
382 let context = PrintContext {
383 builtin_is_significant: true,
384 ..PRINT_CONTEXT
385 };
386 let result = pf.execute(&functions, &context).unwrap();
387 assert_eq!(result, "foo() { echo; }\ntypeset -f foo\n");
388 }
389
390 #[test]
391 fn insignificant_builtin_with_attributed_function() {
392 let mut functions = FunctionSet::<()>::new();
393 let foo = Function::new(
394 "foo",
395 function_body_stub("{ echo; }"),
396 Location::dummy("definition location"),
397 )
398 .make_read_only(Location::dummy("readonly location"));
399 functions.define(foo).unwrap();
400 let pf = PrintFunctions {
401 functions: Field::dummies(["foo"]),
402 attrs: vec![],
403 };
404
405 let context = PrintContext {
406 builtin_is_significant: false,
407 ..PRINT_CONTEXT
408 };
409 let result = pf.clone().execute(&functions, &context).unwrap();
410 assert_eq!(result, "foo() { echo; }\ntypeset -fr foo\n");
411 }
412
413 #[test]
414 fn options_allowed() {
415 let mut functions = FunctionSet::<()>::new();
416 let foo = Function::new(
417 "foo",
418 function_body_stub("{ echo; }"),
419 Location::dummy("definition location"),
420 )
421 .make_read_only(Location::dummy("readonly location"));
422 functions.define(foo).unwrap();
423 let pf = PrintFunctions {
424 functions: Field::dummies(["foo"]),
425 attrs: vec![],
426 };
427
428 let context = PrintContext {
429 options_allowed: &[],
430 ..PRINT_CONTEXT
431 };
432 let result = pf.clone().execute(&functions, &context).unwrap();
433 assert_eq!(result, "foo() { echo; }\n");
434
435 let context = PrintContext {
436 options_allowed: &[READONLY_OPTION],
437 ..PRINT_CONTEXT
438 };
439 let result = pf.execute(&functions, &context).unwrap();
440 assert_eq!(result, "foo() { echo; }\ntypeset -fr foo\n");
441 }
442 }
443}