tmc_langs_framework/
policy.rs1use crate::{TmcError, TmcProjectYml};
4use std::path::Path;
5
6pub trait StudentFilePolicy {
12 fn new_with_project_config(project_config: TmcProjectYml) -> Self
14 where
15 Self: Sized;
16
17 fn new(project_dir: &Path) -> Result<Self, TmcError>
19 where
20 Self: Sized,
21 {
22 let project_config = TmcProjectYml::load_or_default(project_dir)?;
23 Ok(Self::new_with_project_config(project_config))
24 }
25
26 fn get_project_config(&self) -> &TmcProjectYml;
28
29 fn is_student_file(&self, file_path: &Path) -> bool {
39 if file_path == Path::new(".tmcproject.yml") {
41 return false;
42 }
43
44 let config = self.get_project_config();
46 let is_extra_student_file = config
47 .extra_student_files
48 .iter()
49 .any(|f| file_path.starts_with(f));
50
51 let is_extra_exercise_file = config
52 .extra_exercise_files
53 .iter()
54 .any(|f| file_path.starts_with(f));
55
56 is_extra_student_file
58 || (self.is_non_extra_student_file(file_path) && !is_extra_exercise_file)
59 }
60
61 fn is_non_extra_student_file(&self, file_path: &Path) -> bool;
64
65 fn is_updating_forced(&self, path: &Path) -> Result<bool, TmcError> {
69 for force_update_path in &self.get_project_config().force_update {
70 if path.starts_with(force_update_path) {
71 return Ok(true);
72 }
73 }
74 Ok(false)
75 }
76}
77
78pub struct NothingIsStudentFilePolicy {
81 project_config: TmcProjectYml,
82}
83
84impl StudentFilePolicy for NothingIsStudentFilePolicy {
85 fn new(_project_dir: &Path) -> Result<Self, TmcError>
86 where
87 Self: Sized,
88 {
89 let project_config = TmcProjectYml {
90 ..Default::default()
91 };
92 Ok(Self { project_config })
93 }
94
95 fn new_with_project_config(_project_config: TmcProjectYml) -> Self
96 where
97 Self: Sized,
98 {
99 let project_config = TmcProjectYml {
100 ..Default::default()
101 };
102 Self { project_config }
103 }
104
105 fn get_project_config(&self) -> &TmcProjectYml {
106 &self.project_config
107 }
108
109 fn is_non_extra_student_file(&self, _path: &Path) -> bool {
110 false
111 }
112}
113
114#[derive(Default)]
117pub struct EverythingIsStudentFilePolicy {
118 project_config: TmcProjectYml,
119}
120
121impl StudentFilePolicy for EverythingIsStudentFilePolicy {
122 fn new(_project_dir: &Path) -> Result<Self, TmcError>
123 where
124 Self: Sized,
125 {
126 let project_config = TmcProjectYml {
127 ..Default::default()
128 };
129 Ok(Self { project_config })
130 }
131
132 fn new_with_project_config(_project_config: TmcProjectYml) -> Self
133 where
134 Self: Sized,
135 {
136 let project_config = TmcProjectYml {
137 ..Default::default()
138 };
139 Self { project_config }
140 }
141
142 fn get_project_config(&self) -> &TmcProjectYml {
143 &self.project_config
144 }
145
146 fn is_non_extra_student_file(&self, _path: &Path) -> bool {
147 true
148 }
149}
150
151#[cfg(test)]
152#[allow(clippy::unwrap_used)]
153mod test {
154 use super::*;
155 use std::path::{Path, PathBuf};
156
157 fn init() {
158 use log::*;
159 use simple_logger::*;
160 let _ = SimpleLogger::new().with_level(LevelFilter::Debug).init();
161 }
162
163 fn file_to(
164 target_dir: impl AsRef<std::path::Path>,
165 target_relative: impl AsRef<std::path::Path>,
166 contents: impl AsRef<[u8]>,
167 ) -> PathBuf {
168 let target = target_dir.as_ref().join(target_relative);
169 if let Some(parent) = target.parent() {
170 std::fs::create_dir_all(parent).unwrap();
171 }
172 std::fs::write(&target, contents.as_ref()).unwrap();
173 target
174 }
175
176 struct MockPolicy {
177 project_config: TmcProjectYml,
178 }
179
180 impl StudentFilePolicy for MockPolicy {
181 fn new_with_project_config(project_config: TmcProjectYml) -> Self
182 where
183 Self: Sized,
184 {
185 Self { project_config }
186 }
187
188 fn get_project_config(&self) -> &TmcProjectYml {
189 &self.project_config
190 }
191
192 fn is_non_extra_student_file(&self, file_path: &Path) -> bool {
193 file_path
194 .components()
195 .any(|c| c.as_os_str() == "student_file")
196 }
197 }
198
199 #[test]
200 fn considers_student_source_files() {
201 init();
202
203 let temp = tempfile::tempdir().unwrap();
204 file_to(&temp, "dir/student_file/some file", "");
205 file_to(&temp, "other dir/student_file", "");
206 file_to(&temp, "other dir/other file", "");
207 file_to(&temp, "other file", "");
208
209 let project_config = TmcProjectYml::default();
210 let policy = MockPolicy { project_config };
211 assert!(policy.is_student_file(Path::new("dir/student_file/some file")));
212 assert!(policy.is_student_file(Path::new("other dir/student_file")));
213 assert!(!policy.is_student_file(Path::new("other dir/other file")));
214 assert!(!policy.is_student_file(Path::new("other file")));
215 }
216
217 #[test]
218 fn considers_extra_student_files() {
219 init();
220
221 let temp = tempfile::tempdir().unwrap();
222 file_to(&temp, "sdir/some file", "");
223 file_to(&temp, "other dir/sfile", "");
224 file_to(&temp, "other dir/other file", "");
225 file_to(&temp, "other file", "");
226
227 let project_config = TmcProjectYml {
228 extra_student_files: vec![PathBuf::from("sdir"), PathBuf::from("other dir/sfile")],
229 ..Default::default()
230 };
231 let policy = MockPolicy { project_config };
232 assert!(policy.is_student_file(Path::new("sdir/some file")));
233 assert!(policy.is_student_file(Path::new("other dir/sfile")));
234 assert!(!policy.is_student_file(Path::new("other dir/other file")));
235 assert!(!policy.is_student_file(Path::new("other file")));
236 }
237
238 #[test]
239 fn considers_force_uodate_paths() {
240 init();
241
242 let temp = tempfile::tempdir().unwrap();
243 file_to(&temp, "sdir/some file", "");
244 file_to(&temp, "other dir/sfile", "");
245 file_to(&temp, "other dir/other file", "");
246 file_to(&temp, "other file", "");
247
248 let project_config = TmcProjectYml {
249 force_update: vec![PathBuf::from("sdir"), PathBuf::from("other dir/sfile")],
250 ..Default::default()
251 };
252 let policy = MockPolicy { project_config };
253 assert!(
254 policy
255 .is_updating_forced(Path::new("sdir/some file"))
256 .unwrap()
257 );
258 assert!(
259 policy
260 .is_updating_forced(Path::new("other dir/sfile"))
261 .unwrap()
262 );
263 assert!(
264 !policy
265 .is_updating_forced(Path::new("other dir/other file"))
266 .unwrap()
267 );
268 assert!(!policy.is_updating_forced(Path::new("other file")).unwrap());
269 }
270
271 #[test]
272 fn is_object_safe() {
273 fn _f(_: Box<dyn StudentFilePolicy>) {}
275 }
276}