Skip to main content

yash_builtin/typeset/
print_functions.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17use super::*;
18use std::fmt::Write;
19use yash_env::function::Function;
20use yash_env::function::FunctionSet;
21
22impl PrintFunctions {
23    /// Executes the command.
24    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            // TODO Honor the collation order in the locale.
35            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    // Skip if the function does not match the filter.
63    if filter_attrs
64        .iter()
65        .any(|&(attr, state)| attr.test(function) != state)
66    {
67        return;
68    }
69
70    // Print the function body.
71    let name = yash_quote::quoted(&function.name);
72    if name.needs_quoting() {
73        output.push_str("function ");
74    }
75    // TODO multiline pretty printing
76    writeln!(output, "{}() {}", name, function.body).unwrap();
77
78    // Print a command to set the function attributes.
79    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        // The result is sorted by function name.
201        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}