use aurora_core::{AuroraResult, Pipeline, Value};
use std::fs;
use std::io::{BufRead, BufReader};
use regex::Regex;
fn is_binary(data: &[u8]) -> bool {
data[..data.len().min(1024)].contains(&0)
}
pub fn text_grep(pattern: &str, path: &str) -> AuroraResult<Pipeline> {
let re = Regex::new(pattern)
.map_err(|e| aurora_core::AuroraError::InvalidInput(
format!("invalid regex: {e}")
))?;
let path = std::path::Path::new(path);
let mut rows: Vec<Vec<Value>> = Vec::new();
if path.is_file() {
let data = fs::read(path)
.map_err(|e| aurora_core::AuroraError::Io(e))?;
if is_binary(&data) {
return Err(aurora_core::AuroraError::InvalidInput(
format!("binary file: {}", path.display())
));
}
let file = fs::File::open(path)
.map_err(|e| aurora_core::AuroraError::Io(e))?;
let reader = BufReader::new(file);
for (i, line) in reader.lines().enumerate() {
let line = line.map_err(|e| aurora_core::AuroraError::Io(e))?;
if re.find(&line).is_some() {
rows.push(vec![
Value::Int((i + 1) as i64),
Value::String(line),
]);
}
}
} else if path.is_dir() {
for entry in walkdir::WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
if !entry.file_type().is_file() {
continue;
}
let data = match fs::read(entry.path()) {
Ok(d) => d,
Err(_) => continue,
};
if is_binary(&data) {
continue;
}
let file_path = entry.path().to_string_lossy().to_string();
let content = match String::from_utf8(data) {
Ok(c) => c,
Err(_) => continue,
};
for (i, line) in content.lines().enumerate() {
if re.find(line).is_some() {
rows.push(vec![
Value::String(file_path.clone()),
Value::Int((i + 1) as i64),
Value::String(line.into()),
]);
}
}
}
}
let headers = if path.is_file() {
vec!["line".into(), "content".into()]
} else {
vec!["file".into(), "line".into(), "content".into()]
};
Ok(Pipeline::table(headers, rows))
}
pub fn text_replace(pattern: &str, replacement: &str, path_str: &str) -> AuroraResult<Pipeline> {
let re = Regex::new(pattern)
.map_err(|e| aurora_core::AuroraError::InvalidInput(
format!("invalid regex: {e}")
))?;
let path = std::path::Path::new(path_str);
let data = fs::read(path)
.map_err(|e| aurora_core::AuroraError::Io(e))?;
if is_binary(&data) {
return Err(aurora_core::AuroraError::InvalidInput(
format!("binary file: {path_str}")
));
}
let content = String::from_utf8(data)
.map_err(|_| aurora_core::AuroraError::InvalidInput(
format!("not valid UTF-8: {path_str}")
))?;
let result = re.replace_all(&content, replacement).to_string();
let mut count = 0;
for _mat in re.find_iter(&content) {
count += 1;
}
fs::write(path, &result)
.map_err(|e| aurora_core::AuroraError::Io(e))?;
Ok(Pipeline::table(
vec!["action".into(), "file".into(), "replacements".into()],
vec![vec![
Value::String("replace".into()),
Value::String(path_str.into()),
Value::Int(count as i64),
]],
))
}
pub fn text_transform(path_str: &str, upper: bool, lower: bool) -> AuroraResult<Pipeline> {
let path = std::path::Path::new(path_str);
let data = fs::read(path)
.map_err(|e| aurora_core::AuroraError::Io(e))?;
if is_binary(&data) {
return Err(aurora_core::AuroraError::InvalidInput(
format!("binary file: {path_str}")
));
}
let content = String::from_utf8(data)
.map_err(|_| aurora_core::AuroraError::InvalidInput(
format!("not valid UTF-8: {path_str}")
))?;
let result = if upper {
content.to_uppercase()
} else if lower {
content.to_lowercase()
} else {
return Err(aurora_core::AuroraError::InvalidInput(
"specify --upper or --lower".into()
));
};
fs::write(path, &result)
.map_err(|e| aurora_core::AuroraError::Io(e))?;
let action = if upper { "upper" } else { "lower" };
Ok(Pipeline::table(
vec!["action".into(), "file".into()],
vec![vec![
Value::String(action.into()),
Value::String(path_str.into()),
]],
))
}