Skip to main content

yash_builtin/typeset/
set_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::rc::Rc;
19use yash_env::function::FunctionSet;
20
21impl SetFunctions {
22    /// Executes the command.
23    pub fn execute<S>(self, functions: &mut FunctionSet<S>) -> Result<String, Vec<ExecuteError>> {
24        let mut errors = Vec::new();
25
26        for name in self.functions {
27            for &(attr, state) in &self.attrs {
28                match (attr, state) {
29                    (FunctionAttr::ReadOnly, State::On) => {
30                        match functions.unset(&name.value) {
31                            Ok(None) => {
32                                errors.push(ExecuteError::ModifyUnsetFunction(name.clone()));
33                            }
34
35                            Err(_) => { /* The function is already read-only, do nothing. */ }
36
37                            Ok(Some(mut function)) => {
38                                Rc::make_mut(&mut function).read_only_location =
39                                    Some(name.origin.clone());
40                                let define_result = functions.define(function);
41                                // The define function fails only when the set has a read-only
42                                // function with the same name. This is impossible because we have
43                                // just unset the function.
44                                debug_assert!(
45                                    define_result.is_ok(),
46                                    "the function should be defined successfully: {define_result:?}"
47                                );
48                            }
49                        }
50                    }
51
52                    (FunctionAttr::ReadOnly, State::Off) => match functions.get(&name.value) {
53                        None => errors.push(ExecuteError::ModifyUnsetFunction(name.clone())),
54
55                        Some(function) => {
56                            if let Some(read_only_location) = function.read_only_location.clone() {
57                                errors.push(ExecuteError::UndoReadOnlyFunction(
58                                    UndoReadOnlyError {
59                                        name: name.clone(),
60                                        read_only_location,
61                                    },
62                                ));
63                            }
64                        }
65                    },
66                }
67            }
68        }
69
70        if errors.is_empty() {
71            Ok(String::new())
72        } else {
73            Err(errors)
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use assert_matches::assert_matches;
82    use yash_env::function::Function;
83    use yash_env::test_helper::function::FunctionBodyStub;
84
85    #[test]
86    fn making_existing_functions_readonly() {
87        let mut functions = FunctionSet::<()>::new();
88        let foo = Function::new(
89            "foo",
90            FunctionBodyStub::rc_dyn(),
91            Location::dummy("foo location"),
92        );
93        let bar = Function::new(
94            "bar",
95            FunctionBodyStub::rc_dyn(),
96            Location::dummy("bar location"),
97        )
98        .make_read_only(Location::dummy("bar readonly location"));
99        functions.define(foo.clone()).unwrap();
100        functions.define(bar.clone()).unwrap();
101        let sf = SetFunctions {
102            functions: Field::dummies(["foo", "bar"]),
103            attrs: vec![(FunctionAttr::ReadOnly, State::On)],
104        };
105        let foo_location = sf.functions[0].origin.clone();
106
107        let result = sf.execute(&mut functions);
108
109        assert_eq!(result, Ok("".to_string()));
110        assert_eq!(
111            **functions.get("foo").unwrap(),
112            foo.make_read_only(foo_location),
113        );
114        // No change for bar because it is already read-only.
115        assert_eq!(**functions.get("bar").unwrap(), bar);
116    }
117
118    #[test]
119    fn unsetting_readonly_attribute_of_existing_functions() {
120        let mut functions = FunctionSet::<()>::new();
121        let foo = Function::new(
122            "foo",
123            FunctionBodyStub::rc_dyn(),
124            Location::dummy("foo location"),
125        );
126        let bar_location = Location::dummy("bar readonly location");
127        let bar = Function::new(
128            "bar",
129            FunctionBodyStub::rc_dyn(),
130            Location::dummy("bar location"),
131        )
132        .make_read_only(bar_location.clone());
133        functions.define(foo.clone()).unwrap();
134        functions.define(bar.clone()).unwrap();
135        let sf = SetFunctions {
136            functions: Field::dummies(["foo", "bar"]),
137            attrs: vec![(FunctionAttr::ReadOnly, State::Off)],
138        };
139        let arg_bar = sf.functions[1].clone();
140
141        let errors = sf.execute(&mut functions).unwrap_err();
142
143        assert_matches!(&errors[..], [ExecuteError::UndoReadOnlyFunction(error)] => {
144            assert_eq!(error.name, arg_bar);
145            assert_eq!(error.read_only_location, bar_location);
146        });
147        assert_eq!(**functions.get("foo").unwrap(), foo);
148        assert_eq!(**functions.get("bar").unwrap(), bar);
149    }
150
151    #[test]
152    fn making_non_existing_function_readonly() {
153        let mut functions = FunctionSet::<()>::new();
154        let sf = SetFunctions {
155            functions: Field::dummies(["foo"]),
156            attrs: vec![(FunctionAttr::ReadOnly, State::On)],
157        };
158        let arg_foo = sf.functions[0].clone();
159
160        let errors = sf.execute(&mut functions).unwrap_err();
161
162        assert_eq!(errors, [ExecuteError::ModifyUnsetFunction(arg_foo)]);
163        assert_eq!(functions.len(), 0);
164    }
165
166    #[test]
167    fn unsetting_readonly_attribute_of_non_existing_functions() {
168        let mut functions = FunctionSet::<()>::new();
169        let sf = SetFunctions {
170            functions: Field::dummies(["foo"]),
171            attrs: vec![(FunctionAttr::ReadOnly, State::Off)],
172        };
173        let arg_foo = sf.functions[0].clone();
174
175        let errors = sf.execute(&mut functions).unwrap_err();
176
177        assert_eq!(errors, [ExecuteError::ModifyUnsetFunction(arg_foo)]);
178        assert_eq!(functions.len(), 0);
179    }
180}