use aurora_core::{AuroraResult, Pipeline, Value};
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::time::UNIX_EPOCH;
use walkdir::WalkDir;
use regex::Regex;
pub fn fs_ls(path: &str, long: bool) -> AuroraResult<Pipeline> {
let dir = fs::read_dir(path)
.map_err(|e| aurora_core::AuroraError::Io(e))?;
let mut rows: Vec<Vec<Value>> = Vec::new();
for entry in dir {
let entry = entry.map_err(|e| aurora_core::AuroraError::Io(e))?;
let name = entry.file_name().to_string_lossy().to_string();
let meta = entry.metadata().ok();
let size = meta.as_ref().map(|m| m.len() as i64).unwrap_or(0);
let file_type = if meta.as_ref().map(|m| m.is_dir()).unwrap_or(false) {
"dir"
} else if meta.as_ref().map(|m| m.is_symlink()).unwrap_or(false) {
"symlink"
} else {
"file"
};
let modified = meta.as_ref()
.and_then(|m| m.modified().ok())
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
if long {
rows.push(vec![
Value::String(name),
Value::Int(size),
Value::String(file_type.into()),
Value::Int(modified),
]);
} else {
rows.push(vec![Value::String(name)]);
}
}
let headers = if long {
vec!["name".into(), "size".into(), "type".into(), "modified".into()]
} else {
vec!["name".into()]
};
Ok(Pipeline::table(headers, rows))
}
pub fn fs_tree(path: &str, depth: Option<usize>) -> AuroraResult<Pipeline> {
let mut walker = WalkDir::new(path);
if let Some(d) = depth {
walker = walker.max_depth(d);
}
let mut rows: Vec<Vec<Value>> = Vec::new();
for entry in walker.into_iter().filter_map(|e| e.ok()) {
let p = entry.path().to_string_lossy().to_string();
let level = entry.depth() as i64;
rows.push(vec![
Value::String(p),
Value::Int(level),
]);
}
Ok(Pipeline::table(
vec!["path".into(), "level".into()],
rows,
))
}
pub fn fs_find(pattern: &str, path: &str) -> AuroraResult<Pipeline> {
let re = Regex::new(pattern)
.map_err(|e| aurora_core::AuroraError::InvalidInput(
format!("invalid regex: {}", e)
))?;
let mut rows: Vec<Vec<Value>> = Vec::new();
for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
if entry.file_type().is_file() {
let name = entry.file_name().to_string_lossy();
if re.is_match(&name) {
let p = entry.path().to_string_lossy().to_string();
let size = entry.metadata()
.map(|m| m.len() as i64)
.unwrap_or(0);
rows.push(vec![
Value::String(p),
Value::Int(size),
]);
}
}
}
Ok(Pipeline::table(
vec!["path".into(), "size".into()],
rows,
))
}
pub fn fs_info(path: &str) -> AuroraResult<Pipeline> {
let p = Path::new(path);
let meta = fs::metadata(p)
.map_err(|e| aurora_core::AuroraError::Io(e))?;
let size = meta.len() as i64;
let created = meta.created().ok()
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let modified = meta.modified().ok()
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let file_type = if meta.is_dir() {
"directory"
} else if meta.is_symlink() {
"symlink"
} else {
"file"
};
let permissions = format!("{:o}", meta.permissions().mode());
let mut record = std::collections::BTreeMap::new();
record.insert("path".into(), Value::String(path.into()));
record.insert("size".into(), Value::Int(size));
record.insert("created".into(), Value::Int(created));
record.insert("modified".into(), Value::Int(modified));
record.insert("type".into(), Value::String(file_type.into()));
record.insert("permissions".into(), Value::String(permissions));
Ok(Pipeline::single(Value::Record(record)))
}
pub fn fs_du(path: &str) -> AuroraResult<Pipeline> {
let mut rows: Vec<Vec<Value>> = Vec::new();
for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
if entry.file_type().is_dir() {
let p = entry.path().to_string_lossy().to_string();
let mut size: i64 = 0;
if let Ok(dir) = fs::read_dir(entry.path()) {
for sub in dir.flatten() {
if let Ok(meta) = sub.metadata() {
size += meta.len() as i64;
}
}
}
rows.push(vec![
Value::String(p),
Value::Int(size),
]);
}
}
Ok(Pipeline::table(
vec!["path".into(), "size_bytes".into()],
rows,
))
}