1use cel::Context;
2use cel::Value;
3use cel::objects::{Key, Map};
4use cel::{ExecutionError, FunctionContext, ResolveResult};
5
6use std::collections::HashMap;
7use std::os::unix::fs::{FileTypeExt, MetadataExt}; #[allow(unused_imports)] use std::path::{Path, PathBuf}; use std::sync::Arc; fn cel_parse_size(ftx: &FunctionContext, s: Arc<String>) -> ResolveResult {
13 match parse_size::parse_size(&*s) {
14 Ok(size) => Value::Int(size as i64).into(),
15 Err(e) => ExecutionError::function_error(&ftx.name, e).into(),
16 }
17}
18
19pub fn parser2ctx<'a>(ctx: &mut Context<'a>, name: &str) {
20 ctx.add_function(name, cel_parse_size);
21}
22
23pub struct DirentInfo {
24 pub name: String,
25 pub is_file: bool,
26 pub is_dir: bool,
27 pub is_symlink: bool,
28 pub is_block_device: bool,
29 pub is_char_device: bool,
30 pub is_hidden: bool,
31 pub is_readonly: bool,
32 pub is_socket: bool,
33 pub is_fifo: bool,
34 pub len: u64,
35 pub nlink: u64,
36 pub mode: u32,
37 pub uid: u32,
38 pub gid: u32,
39 pub mtime: i64,
40 pub atime: i64,
41 pub ctime: i64,
42}
43
44impl From<&Path> for DirentInfo {
45 fn from(path: &Path) -> Self {
46 let name = path
47 .file_name()
48 .and_then(|os_str| os_str.to_str())
49 .unwrap_or("")
50 .to_string();
51
52 let metadata = path.symlink_metadata().ok(); DirentInfo {
55 name: name.clone(),
56 is_file: metadata.as_ref().is_some_and(|m| m.is_file()),
57 is_dir: metadata.as_ref().is_some_and(|m| m.is_dir()),
58 is_symlink: metadata.as_ref().is_some_and(|m| m.is_symlink()),
59 is_block_device: metadata.as_ref().is_some_and(|m| {
60 #[cfg(unix)]
61 {
62 m.file_type().is_block_device()
63 }
64 #[cfg(not(unix))]
65 {
66 false
67 }
68 }),
69 is_char_device: metadata.as_ref().is_some_and(|m| {
70 #[cfg(unix)]
71 {
72 m.file_type().is_char_device()
73 }
74 #[cfg(not(unix))]
75 {
76 false
77 }
78 }),
79 is_hidden: name.starts_with('.'),
80 is_readonly: metadata
81 .as_ref()
82 .is_some_and(|m| m.permissions().readonly()),
83 is_socket: metadata.as_ref().is_some_and(|m| {
84 #[cfg(unix)]
85 {
86 m.file_type().is_socket()
87 }
88 #[cfg(not(unix))]
89 {
90 false
91 }
92 }),
93 is_fifo: metadata.as_ref().is_some_and(|m| {
94 #[cfg(unix)]
95 {
96 m.file_type().is_fifo()
97 }
98 #[cfg(not(unix))]
99 {
100 false
101 }
102 }),
103 len: metadata.as_ref().map_or(0, |m| m.len()),
104 nlink: metadata.as_ref().map_or(0, |m| {
105 #[cfg(unix)]
106 {
107 m.nlink()
108 }
109 #[cfg(not(unix))]
110 {
111 1
112 }
113 }),
114 mode: metadata.as_ref().map_or(0, |m| {
115 #[cfg(unix)]
116 {
117 m.mode()
118 }
119 #[cfg(not(unix))]
120 {
121 0
122 }
123 }),
124 uid: metadata.as_ref().map_or(0, |m| {
125 #[cfg(unix)]
126 {
127 m.uid()
128 }
129 #[cfg(not(unix))]
130 {
131 0
132 }
133 }),
134 gid: metadata.as_ref().map_or(0, |m| {
135 #[cfg(unix)]
136 {
137 m.gid()
138 }
139 #[cfg(not(unix))]
140 {
141 0
142 }
143 }),
144 mtime: metadata.as_ref().map_or(0, |m| {
145 m.modified()
146 .map(|st| {
147 st.duration_since(std::time::UNIX_EPOCH)
148 .unwrap_or_default()
149 .as_secs() as i64
150 })
151 .unwrap_or(0)
152 }),
153 atime: metadata.as_ref().map_or(0, |m| {
154 m.accessed()
155 .map(|st| {
156 st.duration_since(std::time::UNIX_EPOCH)
157 .unwrap_or_default()
158 .as_secs() as i64
159 })
160 .unwrap_or(0)
161 }),
162 ctime: metadata.as_ref().map_or(0, |m| {
163 #[cfg(unix)]
164 {
165 m.ctime()
166 }
167 #[cfg(not(unix))]
168 {
169 0
170 }
171 }),
172 }
173 }
174}
175
176impl From<DirentInfo> for Value {
177 fn from(dirent_info: DirentInfo) -> Self {
178 let mut map = Map {
179 map: Arc::new(HashMap::new()),
180 };
181
182 if let Some(internal_map) = Arc::get_mut(&mut map.map) {
183 internal_map.insert(
184 Key::String(Arc::new("name".to_string())),
185 Value::String(dirent_info.name.into()),
186 );
187 internal_map.insert(
188 Key::String(Arc::new("is_file".to_string())),
189 Value::Bool(dirent_info.is_file),
190 );
191 internal_map.insert(
192 Key::String(Arc::new("is_dir".to_string())),
193 Value::Bool(dirent_info.is_dir),
194 );
195 internal_map.insert(
196 Key::String(Arc::new("is_symlink".to_string())),
197 Value::Bool(dirent_info.is_symlink),
198 );
199 internal_map.insert(
200 Key::String(Arc::new("is_block_device".to_string())),
201 Value::Bool(dirent_info.is_block_device),
202 );
203 internal_map.insert(
204 Key::String(Arc::new("is_char_device".to_string())),
205 Value::Bool(dirent_info.is_char_device),
206 );
207 internal_map.insert(
208 Key::String(Arc::new("is_hidden".to_string())),
209 Value::Bool(dirent_info.is_hidden),
210 );
211 internal_map.insert(
212 Key::String(Arc::new("is_readonly".to_string())),
213 Value::Bool(dirent_info.is_readonly),
214 );
215 internal_map.insert(
216 Key::String(Arc::new("is_socket".to_string())),
217 Value::Bool(dirent_info.is_socket),
218 );
219 internal_map.insert(
220 Key::String(Arc::new("is_fifo".to_string())),
221 Value::Bool(dirent_info.is_fifo),
222 );
223 internal_map.insert(
224 Key::String(Arc::new("len".to_string())),
225 Value::Int(dirent_info.len as i64),
226 );
227 internal_map.insert(
228 Key::String(Arc::new("nlink".to_string())),
229 Value::Int(dirent_info.nlink as i64),
230 );
231 internal_map.insert(
232 Key::String(Arc::new("mode".to_string())),
233 Value::Int(dirent_info.mode as i64),
234 );
235 internal_map.insert(
236 Key::String(Arc::new("uid".to_string())),
237 Value::Int(dirent_info.uid as i64),
238 );
239 internal_map.insert(
240 Key::String(Arc::new("gid".to_string())),
241 Value::Int(dirent_info.gid as i64),
242 );
243 internal_map.insert(
244 Key::String(Arc::new("mtime".to_string())),
245 Value::Int(dirent_info.mtime),
246 );
247 internal_map.insert(
248 Key::String(Arc::new("atime".to_string())),
249 Value::Int(dirent_info.atime),
250 );
251 internal_map.insert(
252 Key::String(Arc::new("ctime".to_string())),
253 Value::Int(dirent_info.ctime),
254 );
255 }
256
257 Value::Map(map)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use std::fs;
265 use std::io::Write;
266 use std::path::PathBuf;
267 use tempfile::tempdir;
268
269 #[test]
270 fn test_dirent_info_from_path_file() {
271 let dir = tempdir().unwrap();
272 let file_path = dir.path().join("test_file.txt");
273 fs::File::create(&file_path)
274 .unwrap()
275 .write_all(b"hello")
276 .unwrap();
277
278 let metadata = fs::symlink_metadata(&file_path).unwrap();
279 let expected_len = metadata.len();
280
281 let dirent_info = DirentInfo::from(file_path.as_path());
282
283 assert_eq!(dirent_info.name, "test_file.txt");
284 assert!(dirent_info.is_file);
285 assert!(!dirent_info.is_dir);
286 assert!(!dirent_info.is_symlink);
287 assert_eq!(dirent_info.len, expected_len);
288 }
289
290 #[test]
291 fn test_dirent_info_from_path_dir() {
292 let dir = tempdir().unwrap();
293 let subdir_path = dir.path().join("test_dir");
294 fs::create_dir(&subdir_path).unwrap();
295
296 let dirent_info = DirentInfo::from(subdir_path.as_path());
297
298 assert_eq!(dirent_info.name, "test_dir");
299 assert!(!dirent_info.is_file);
300 assert!(dirent_info.is_dir);
301 assert!(!dirent_info.is_symlink);
302 }
303
304 #[test]
305 fn test_dirent_info_from_path_symlink() {
306 let dir = tempdir().unwrap();
307 let target_file = dir.path().join("target.txt");
308 fs::File::create(&target_file).unwrap();
309 let symlink_path = dir.path().join("link_to_target.txt");
310 #[cfg(unix)] std::os::unix::fs::symlink(&target_file, &symlink_path).unwrap();
312
313 #[cfg(unix)]
315 {
316 let dirent_info = DirentInfo::from(symlink_path.as_path());
317 assert_eq!(dirent_info.name, "link_to_target.txt");
318 assert!(!dirent_info.is_file); assert!(!dirent_info.is_dir); assert!(dirent_info.is_symlink);
321 }
322 }
323
324 #[test]
325 fn test_dirent_info_non_existent_path() {
326 let path = PathBuf::from("non_existent_file.txt");
327 let dirent_info = DirentInfo::from(path.as_path());
328
329 assert_eq!(dirent_info.name, "non_existent_file.txt");
330 assert!(!dirent_info.is_file);
331 assert!(!dirent_info.is_dir);
332 assert!(!dirent_info.is_symlink);
333 assert_eq!(dirent_info.len, 0);
334 assert_eq!(dirent_info.nlink, 0);
335 assert_eq!(dirent_info.mtime, 0);
336 }
337
338 #[test]
339 fn test_dirent_info_to_cel_value() {
340 let dirent_info = DirentInfo {
341 name: "example.txt".to_string(),
342 is_file: true,
343 is_dir: false,
344 is_symlink: false,
345 is_block_device: false,
346 is_char_device: false,
347 is_hidden: false,
348 is_readonly: true,
349 is_socket: false,
350 is_fifo: false,
351 len: 123,
352 nlink: 1,
353 mode: 0o644,
354 uid: 1000,
355 gid: 1000,
356 mtime: 1678886400, atime: 1678886400,
358 ctime: 1678886400,
359 };
360
361 let cel_value: Value = dirent_info.into();
362
363 if let Value::Map(map) = cel_value {
364 let internal_map = map.map.as_ref();
365
366 assert_eq!(
367 internal_map.get(&Key::String(Arc::new("name".to_string()))),
368 Some(&Value::String("example.txt".to_string().into()))
369 );
370 assert_eq!(
371 internal_map.get(&Key::String(Arc::new("is_file".to_string()))),
372 Some(&Value::Bool(true))
373 );
374 assert_eq!(
375 internal_map.get(&Key::String(Arc::new("is_dir".to_string()))),
376 Some(&Value::Bool(false))
377 );
378 assert_eq!(
379 internal_map.get(&Key::String(Arc::new("is_symlink".to_string()))),
380 Some(&Value::Bool(false))
381 );
382 assert_eq!(
383 internal_map.get(&Key::String(Arc::new("is_block_device".to_string()))),
384 Some(&Value::Bool(false))
385 );
386 assert_eq!(
387 internal_map.get(&Key::String(Arc::new("is_char_device".to_string()))),
388 Some(&Value::Bool(false))
389 );
390 assert_eq!(
391 internal_map.get(&Key::String(Arc::new("is_hidden".to_string()))),
392 Some(&Value::Bool(false))
393 );
394 assert_eq!(
395 internal_map.get(&Key::String(Arc::new("is_readonly".to_string()))),
396 Some(&Value::Bool(true))
397 );
398 assert_eq!(
399 internal_map.get(&Key::String(Arc::new("is_socket".to_string()))),
400 Some(&Value::Bool(false))
401 );
402 assert_eq!(
403 internal_map.get(&Key::String(Arc::new("is_fifo".to_string()))),
404 Some(&Value::Bool(false))
405 );
406 assert_eq!(
407 internal_map.get(&Key::String(Arc::new("len".to_string()))),
408 Some(&Value::Int(123))
409 );
410 assert_eq!(
411 internal_map.get(&Key::String(Arc::new("nlink".to_string()))),
412 Some(&Value::Int(1))
413 );
414 assert_eq!(
415 internal_map.get(&Key::String(Arc::new("mode".to_string()))),
416 Some(&Value::Int(0o644))
417 );
418 assert_eq!(
419 internal_map.get(&Key::String(Arc::new("uid".to_string()))),
420 Some(&Value::Int(1000))
421 );
422 assert_eq!(
423 internal_map.get(&Key::String(Arc::new("gid".to_string()))),
424 Some(&Value::Int(1000))
425 );
426 assert_eq!(
427 internal_map.get(&Key::String(Arc::new("mtime".to_string()))),
428 Some(&Value::Int(1678886400))
429 );
430 assert_eq!(
431 internal_map.get(&Key::String(Arc::new("atime".to_string()))),
432 Some(&Value::Int(1678886400))
433 );
434 assert_eq!(
435 internal_map.get(&Key::String(Arc::new("ctime".to_string()))),
436 Some(&Value::Int(1678886400))
437 );
438 } else {
439 panic!("Expected a CEL Map Value");
440 }
441 }
442}