1use aurora_core::{AuroraResult, Pipeline, Value};
2use std::fs;
3use std::os::unix::fs::PermissionsExt;
4use std::path::Path;
5use std::time::UNIX_EPOCH;
6use walkdir::WalkDir;
7use regex::Regex;
8
9pub fn fs_ls(path: &str, long: bool) -> AuroraResult<Pipeline> {
10 let dir = fs::read_dir(path)
11 .map_err(|e| aurora_core::AuroraError::Io(e))?;
12
13 let mut rows: Vec<Vec<Value>> = Vec::new();
14
15 for entry in dir {
16 let entry = entry.map_err(|e| aurora_core::AuroraError::Io(e))?;
17 let name = entry.file_name().to_string_lossy().to_string();
18 let meta = entry.metadata().ok();
19
20 let size = meta.as_ref().map(|m| m.len() as i64).unwrap_or(0);
21 let file_type = if meta.as_ref().map(|m| m.is_dir()).unwrap_or(false) {
22 "dir"
23 } else if meta.as_ref().map(|m| m.is_symlink()).unwrap_or(false) {
24 "symlink"
25 } else {
26 "file"
27 };
28 let modified = meta.as_ref()
29 .and_then(|m| m.modified().ok())
30 .and_then(|t| t.duration_since(UNIX_EPOCH).ok())
31 .map(|d| d.as_secs() as i64)
32 .unwrap_or(0);
33
34 if long {
35 rows.push(vec![
36 Value::String(name),
37 Value::Int(size),
38 Value::String(file_type.into()),
39 Value::Int(modified),
40 ]);
41 } else {
42 rows.push(vec![Value::String(name)]);
43 }
44 }
45
46 let headers = if long {
47 vec!["name".into(), "size".into(), "type".into(), "modified".into()]
48 } else {
49 vec!["name".into()]
50 };
51
52 Ok(Pipeline::table(headers, rows))
53}
54
55pub fn fs_tree(path: &str, depth: Option<usize>) -> AuroraResult<Pipeline> {
56 let mut walker = WalkDir::new(path);
57 if let Some(d) = depth {
58 walker = walker.max_depth(d);
59 }
60
61 let mut rows: Vec<Vec<Value>> = Vec::new();
62
63 for entry in walker.into_iter().filter_map(|e| e.ok()) {
64 let p = entry.path().to_string_lossy().to_string();
65 let level = entry.depth() as i64;
66 rows.push(vec![
67 Value::String(p),
68 Value::Int(level),
69 ]);
70 }
71
72 Ok(Pipeline::table(
73 vec!["path".into(), "level".into()],
74 rows,
75 ))
76}
77
78pub fn fs_find(pattern: &str, path: &str) -> AuroraResult<Pipeline> {
79 let re = Regex::new(pattern)
80 .map_err(|e| aurora_core::AuroraError::InvalidInput(
81 format!("invalid regex: {}", e)
82 ))?;
83
84 let mut rows: Vec<Vec<Value>> = Vec::new();
85
86 for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
87 if entry.file_type().is_file() {
88 let name = entry.file_name().to_string_lossy();
89 if re.is_match(&name) {
90 let p = entry.path().to_string_lossy().to_string();
91 let size = entry.metadata()
92 .map(|m| m.len() as i64)
93 .unwrap_or(0);
94 rows.push(vec![
95 Value::String(p),
96 Value::Int(size),
97 ]);
98 }
99 }
100 }
101
102 Ok(Pipeline::table(
103 vec!["path".into(), "size".into()],
104 rows,
105 ))
106}
107
108pub fn fs_info(path: &str) -> AuroraResult<Pipeline> {
109 let p = Path::new(path);
110 let meta = fs::metadata(p)
111 .map_err(|e| aurora_core::AuroraError::Io(e))?;
112
113 let size = meta.len() as i64;
114 let created = meta.created().ok()
115 .and_then(|t| t.duration_since(UNIX_EPOCH).ok())
116 .map(|d| d.as_secs() as i64)
117 .unwrap_or(0);
118 let modified = meta.modified().ok()
119 .and_then(|t| t.duration_since(UNIX_EPOCH).ok())
120 .map(|d| d.as_secs() as i64)
121 .unwrap_or(0);
122 let file_type = if meta.is_dir() {
123 "directory"
124 } else if meta.is_symlink() {
125 "symlink"
126 } else {
127 "file"
128 };
129 let permissions = format!("{:o}", meta.permissions().mode());
130
131 let mut record = std::collections::BTreeMap::new();
132 record.insert("path".into(), Value::String(path.into()));
133 record.insert("size".into(), Value::Int(size));
134 record.insert("created".into(), Value::Int(created));
135 record.insert("modified".into(), Value::Int(modified));
136 record.insert("type".into(), Value::String(file_type.into()));
137 record.insert("permissions".into(), Value::String(permissions));
138
139 Ok(Pipeline::single(Value::Record(record)))
140}
141
142pub fn fs_du(path: &str) -> AuroraResult<Pipeline> {
143 let mut rows: Vec<Vec<Value>> = Vec::new();
144
145 for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
146 if entry.file_type().is_dir() {
147 let p = entry.path().to_string_lossy().to_string();
148 let mut size: i64 = 0;
149 if let Ok(dir) = fs::read_dir(entry.path()) {
150 for sub in dir.flatten() {
151 if let Ok(meta) = sub.metadata() {
152 size += meta.len() as i64;
153 }
154 }
155 }
156 rows.push(vec![
157 Value::String(p),
158 Value::Int(size),
159 ]);
160 }
161 }
162
163 Ok(Pipeline::table(
164 vec!["path".into(), "size_bytes".into()],
165 rows,
166 ))
167}